什么是开闭原则

开闭原则(Open-Closed Principle,简称 OCP)是面向对象设计中最基础、最重要的一条原则。它是著名的"SOLID"五大设计原则中的"O"。

用最精炼的一句话来概括就是:软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。

这听起来有点矛盾:不修改代码,怎么增加新功能呢?我们还是用生活中的例子和你的代码来"深入浅出"地拆解一下。


1. 通俗比喻:电脑的 USB 接口

想象一下你的电脑。

  • 对扩展开放: 你的电脑可以通过 USB 接口连接无数种外设------鼠标、键盘、打印机、摄像头、甚至是小风扇。只要这个设备符合 USB 标准,插上就能用,这就叫"对扩展开放"。电脑的功能因此被无限放大了。
  • 对修改关闭: 当你想加一个新鼠标时,你不需要把电脑机箱拆开,拿电烙铁把鼠标的线焊接到主板上。电脑的主板被封装在机箱里,对普通用户的物理修改是关闭的。

在写代码时,我们的目标就是把系统设计成这台电脑:定义好标准的"接口",当有新需求时,写一个新的类去实现这个接口(插上新 U 盘),而不是去改动原有的老代码(拆开机箱焊主板)。


2. 反面教材:违反开闭原则的代码

如果不遵守开闭原则,你的代码会变成什么样?我们以你刚才的 PDF 页码为例,假设不用策略模式,而是在一个服务类里用 if-else 硬写:

复制代码
public class PdfPageService {
    
    // 每次有新样式,都要来修改这个核心方法
    public PageNumberPosition calculatePosition(String style, int pageNum, double pageWidth, double textWidth) {
        if ("CENTER".equals(style)) {
            return new PageNumberPosition((pageWidth - textWidth) / 2, Center);
        } else if ("LEFT".equals(style)) {
            return new PageNumberPosition(70, Left);
        } else if ("RIGHT".equals(style)) {
            return new PageNumberPosition(pageWidth - textWidth - 80, Right);
        } 
        // 🚨 警报!产品经理要求加一个"顶部居中"!
        // 你不得不修改这个已经稳定运行了半年的 calculatePosition 方法,
        // 加上一段 else if ("TOP_CENTER".equals(style)) { ... }
        return null;
    }
}

这样做的致命问题是:

修改老代码极容易引入新的 Bug。你明明只是想加个"顶部居中",结果手一抖,把原来"CENTER"的计算逻辑删掉了一个括号,导致线上原有功能全部崩溃。这在大型工程中是灾难性的。


3. 正面教材:代码是如何完美遵循开闭原则的

复制代码
import com.aspose.pdf.HorizontalAlignment;

/**
 * PDF页码样式枚举,内嵌定位策略
 */
public enum PdfPageNumberStyle {

    CENTER {
        @Override
        public PageNumberPosition getPosition(int relativePageNum, double pageWidth, double textWidth) {
            return new PageNumberPosition((pageWidth - textWidth) / 2, HorizontalAlignment.Center);
        }
    },
    LEFT {
        @Override
        public PageNumberPosition getPosition(int relativePageNum, double pageWidth, double textWidth) {
            return new PageNumberPosition(LEFT_MARGIN, HorizontalAlignment.Left);
        }
    },
    RIGHT {
        @Override
        public PageNumberPosition getPosition(int relativePageNum, double pageWidth, double textWidth) {
            return new PageNumberPosition(pageWidth - textWidth - RIGHT_MARGIN, HorizontalAlignment.Right);
        }
    },
    /**
     * 双面打印,奇数页右对齐,偶数页左对齐(右侧先起)
     */
    DUPLEX_RIGHT_FIRST {
        @Override
        public PageNumberPosition getPosition(int relativePageNum, double pageWidth, double textWidth) {
            return relativePageNum % 2 == 0
                    ? new PageNumberPosition(LEFT_MARGIN, HorizontalAlignment.Left)
                    : new PageNumberPosition(pageWidth - textWidth - RIGHT_MARGIN, HorizontalAlignment.Right);
        }
    },
    /**
     * 双面打印,奇数页左对齐,偶数页右对齐(左侧先起)
     */
    DUPLEX_LEFT_FIRST {
        @Override
        public PageNumberPosition getPosition(int relativePageNum, double pageWidth, double textWidth) {
            return relativePageNum % 2 != 0
                    ? new PageNumberPosition(LEFT_MARGIN, HorizontalAlignment.Left)
                    : new PageNumberPosition(pageWidth - textWidth - RIGHT_MARGIN, HorizontalAlignment.Right);
        }
    };

    private static final double LEFT_MARGIN = 70;
    private static final double RIGHT_MARGIN = 80;

    public abstract PageNumberPosition getPosition(int relativePageNum, double pageWidth, double textWidth);

    public static class PageNumberPosition {
        private final double x;
        private final int alignment;

        public PageNumberPosition(double x, int alignment) {
            this.x = x;
            this.alignment = alignment;
        }

        public double getX() {
            return x;
        }

        public int getAlignment() {
            return alignment;
        }
    }
}

优雅的枚举策略模式代码。

  1. 接口标准(USB接口): 定义了抽象方法 getPosition(...)
  2. 原有实现(已有的鼠标/键盘): CENTER, LEFT, RIGHT 这些枚举实例。

如果明天产品经理说:"加上'顶部居中'的样式!"

不需要 去修改任何已经存在的 CENTERLEFT 的代码,也不需要去修改调用这些逻辑的服务类(只要服务类是面向抽象编程的)。

你只需要扩展(增加)一个新的枚举项:

Java

复制代码
    // ... 之前的 CENTER, LEFT 等原封不动 ...

    /**
     * 新增需求:顶部居中
     */
    TOP_CENTER {
        @Override
        public PageNumberPosition getPosition(int relativePageNum, double pageWidth, double textWidth) {
            // 这里写顶部居中的特定逻辑
            return new PageNumberPosition((pageWidth - textWidth) / 2, HorizontalAlignment.Center); 
            // (假设你的 PageNumberPosition 能支持 Y 轴,这里只是演示)
        }
    };

这就是对扩展(新增 TOP_CENTER)开放,对修改(不动 CENTER 等老代码和调用方代码)关闭


总结:为什么要死磕开闭原则?

  • 隔离风险: 新增加的代码出 Bug,只会影响新功能,绝不会把老系统搞瘫痪。
  • 利于测试: 你只需要为新写的 TOP_CENTER 补充单元测试,老功能的测试用例根本不需要跑,因为代码一行都没变。
  • 拥抱变化: 需求永远在变,开闭原则让你的架构具备极强的弹性和生命力。

设计模式(比如策略模式、工厂模式、观察者模式等)本质上都是为了实现开闭原则而发明的"套路"。

相关推荐
曹牧7 小时前
OOP:开闭原则
开闭原则
养生技术人1 天前
Oracle OCP认证考试题目详解082系列第5题
运维·数据库·sql·oracle·开闭原则
kylezhao20191 个月前
C# 的开闭原则(OCP)在工控上位机开发中的具体应用
网络·c#·开闭原则
PuEJbfWWgo1 个月前
谐波电流注入应用于电机控制器 附带电机谐波抑制说明文档 注:为解决汽车NvH而开发,旨在消除转矩谐波
开闭原则
腾科IT教育2 个月前
大学生考OCP认证报考与考试全攻略
数据库开发·开闭原则·ocp培训·ocp考试
kylezhao20192 个月前
C#中开放 - 封闭原则(**Open-Closed Principle,OCP**)
服务器·c#·开闭原则
hexionly2 个月前
演示工厂模式和策略模式的基本用法
java·简单工厂模式·策略模式·开闭原则
小白考证进阶中2 个月前
MySQL OCP认证可以考中文?备考难度怎么样?
数据库·mysql·dba·数据库管理·开闭原则·数据库管理员·mysql认证
HL_风神3 个月前
设计原则之开闭原则
c++·学习·设计模式·开闭原则