什么是开闭原则

开闭原则(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 补充单元测试,老功能的测试用例根本不需要跑,因为代码一行都没变。
  • 拥抱变化: 需求永远在变,开闭原则让你的架构具备极强的弹性和生命力。

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

相关推荐
是温不嗜温1 天前
芯茂微 LP7012 双重过流保护机制拆解:DESAT 单次锁存 vs OCP 连续 5 次锁存有何区别?
嵌入式硬件·开闭原则·电源管理·电源芯片·ac-dc
艾利克斯冰4 天前
Java设计模式详解-七大设计原则(持续更新中)
设计模式·uml·开闭原则
计算机安禾18 天前
【c++面向对象编程】第37篇:面向对象设计原则(一):单一职责与开闭原则
开发语言·c++·开闭原则
S1998_1997111609•X1 个月前
Phash的系统通信工程及恶意注入污染蜜罐轮替探测阻断正常通讯协议系统的dog 通用原理及行为阻击至联合国管理清理全栈
安全·百度·哈希算法·量子计算·开闭原则
S1998_1997111609•X1 个月前
哈希树函数洪水泛滥污染孪生镜像导致生物量子信息泄露以钩子而爬虫植入ssd探测
爬虫·网络协议·缓存·哈希算法·开闭原则
S1998_1997111609•X1 个月前
电容〇解临界过流恶意注入污染寄生的边缘锯齿噪声污染孪生
安全·百度·哈希算法·量子计算·开闭原则
S1998_1997111609•X1 个月前
恶意烧录级系统固件开源á进行函数值哈希泛滥污染孪生以钩子而爬虫合规系统的性能指标体系技术应用内存
安全·百度·哈希算法·量子计算·开闭原则
S1998_1997111609•X1 个月前
元组件HCG&&单元量泄露数据爬虫植入syatem,造成系统ioc dark and agent of China gov 的犯罪心理学依据行为
网络协议·安全·百度·哈希算法·开闭原则
S1998_1997111609•X1 个月前
论述情况盀导致系统应用通信通讯协议被恶意注入污染蜜罐开元盀用于非法侵入爬虫植入ssd的通用技术原理
网络·网络协议·百度·哈希算法·开闭原则
S1998_1997111609•X1 个月前
论当今社会主义与人文关怀人格思想下的恶意仿生注入污染蜜罐描述进行函数值非法侵入爬虫的咼忄乂癿〇仺⺋.
数据库·网络协议·百度·ssh·开闭原则