开放封闭原则

First Post:

Last Update:

开放封闭原则

The Open-Closed Principle开闭原则

说的是软件实体(类、模块、函数等)应该是可以拓展,但是不可修改的。

两个特征:

对于拓展是开放的(Open for Extension)

对于修改是封闭的(Closed for modification)

在做任何系统时,不必指望系统需求一开始就是确定的,因为需求一定会更改,要设计出容易维护又不容易出问题的软件系统,最好就是多拓展,少修改。怎么样的设计才能面对需求的改变而保持相对稳定呢?

拓展性问题

  • 保留拓展点
  • 要时刻具备拓展意识、抽象意识和封装意识

修改问题

  • 添加了新类、新模块,但仍需要对高层模块作些许修改以适应新业务。
  • 尽量让修改更集中、更少、更上层,尽量让最核心、最复杂的那部分逻辑代码满足开闭原则。
  • 绝对的对修改关闭是不存在的、不可能的。
  • 无论模块多么“封闭”,都存在无法对之封闭的变化,设计人员必须对他设计的模块应该对哪种变化封闭做出选择,他必须猜测最有可能发生的变化种类,然后抽象隔离出那些变化。
  • 等到变化发生时立即采取行动
  • 在最开始编写时,假设变化不会发生,当变化发生时,我们就创建抽象来隔离发生的同类变化。

例子:运算类——添加一个新的运算

刚开始的时候,这个运算整个写在控制台,如果要添加一个新的运算操作,需要修改整个程序结构。

后面通过抽象,继承的方式,将不同的运算操作分离开来,如果要添加一个新的运算操作,就直接新建一个类继承运算类重写虚方法。

面对需求,对程序的改动是通过增加新代码实现的(拓展),而不是修改原来的代码做出来的(减少修改)。

并非对于应用程序的每个部分都刻意进行抽象,拒绝这样的过度抽象,拒绝不成熟的抽象和学会使用抽象一样重要。

如何做到“对扩展开放、修改关闭”?

实际上,开闭原则讲的就是代码****的扩展性问题,是判断一段代码是否易扩展的“金标准”。

在讲具体的方法论之前,我们先来看一些更加偏向顶层的指导思想。为了尽量写出扩展性好的代码,我们要时刻具备扩展意识、抽象意识、封装意识。这些“潜意识”可能比任何开发技巧都重要。

扩展意识

在写代码的时候,我们要多花点时间往前多思考一下,这段代码未来可能有哪些需求变更、如何设计代码结构,事先留好扩展点,以便在未来需求变更的时候,不需要改动代码整体结构、做到最小代码改动的情况下,新的代码能够灵活地插入到扩展点上,做到“对扩展开放、对修改关闭”。

抽象意识

提供抽象化的不可变接口,给上层系统使用。当具体的实现发生变化的时候,我们只需要基于相同的抽象接口,扩展一个新的实现,替换老的实现即可,上游系统的代码几乎不需要修改。

封装意识

在识别出代码可变部分和不可变部分之后,我们要将可变部分封装起来,隔离变化。

实现

在众多的设计原则、思想、模式中,最常用来提高代码扩展性的方法有:多态、依赖注入、基于接口而非实现编程,以及大部分的设计模式(比如:装饰、策略、模板、职责链、状态等)。

预留扩展点

前面我们提到,写出支持“对扩展开放、对修改关闭”的代码的关键是预留扩展点,那么问题是,应该如何才能识别出所有可能的扩展点呢?

如果开发业务导向的系统,比如电商系统、物流系统、金融系统等,要想识别尽可能多的扩展点,就需要对业务本身有足够多的了解。

如果开发通用、偏底层的框架、类库、组件等,就需要了解它们会被如何使用,日后可能会添加什么功能。

过度设计的问题

“唯一不变的就是变化本身”,尽管我们对业务系统、框架功能有足够多的了解,也不能识别出所有的扩展点。即便我们能够识别出所有的扩展点,为这些地方做预留扩展点的设计,成本都是很大的,这就叫做“过度设计”。

合理的做法

应该是对于一些比较确定的,短期内可能就会扩展,或者需要改动对代码结构影响比较大的情况,或者实现成本不高的扩展点,在编写代码的时候,我们就可以事先做预留扩展点设计。但是对于一些不确定未来是否要支持的需求,或者实现起来比较复杂的扩展点,可以通过重构代码的方式来支持扩展的需求。

设计原则是否应用得当,应该根据具体的业务场景,具体分析。

总结

  1. 对扩展开放,是为了应付变化(需求);

  2. 对修改封闭,是为了保证已有代码的稳定性;

  3. 最终结果是为了让系统更有弹性!