经典力学的基石是牛顿三大定律。而面向对象的可复用设计(Object Oriented Design或OOD)的第一块基石,便是所谓的“开-闭”原则(Open-Closed Principle,常缩写为

 什么是“开-闭”原则

    “开-闭”原则讲的是:一个软件实体应当对扩展开放,对修改关闭。这一原则最早由Bertrand Meyer提出,英文原文是:Software entities should be open for extension, but closed for modification.

    这个原则说的是,在设计一个模块的时候,应当使这个模块可以再不被修改的前提下被扩展。换言之,应当可以再不必修改源代码的情况下改变这个模块的行为。

    所有的软件系统都有一个共同的性质,即对它们的需求都会随时间的推移而发生变化。在软件系统面临新的需求时,系统的设计必须是文档的。满足“开闭”原则的设计可以给一个软件系统两个无可比拟的优越性:

  • 通过扩展已有的软件系统,可以提供新的行为,以满足对软件的新需求,使变化中的软件系统有一定的适应性和灵活性。
  • 已有的软件模块,特别是最重要的抽象层模块不能再修改,这就使变化中的软件系统有一定的稳定性和延续性。

    具有这两个优点的软件系统是一个在高层次上实现了复用的系统,也是一个易于维护的系统。

怎样做到“开闭”原则

    咋看起来,不能修改而可以扩展似乎是自相矛盾的。怎么可以同时又不修改、而又可以扩展呢?在开始讨论之前,不妨考察一下《西游记》中,玉皇大帝在美猴王的挑战之下是怎样维护天庭的秩序的。

玉帝诏安美猴王

    当年大闹天宫时的美猴王便是玉帝天庭的新挑战。美猴王说:“皇帝轮流做,明年到我家。只教他搬出去,将天宫让与我!”对于这项挑战,太白金星给玉帝提出的建议是:“臣启陛下,降一道诏安圣旨,把他宣来上界,与他籍名在箓,一则不动众劳师,二则收仙有道也。”

    换言之,不劳师动众,不破坏天规便是“闭”,收仙有道便是“开”。诏安之法便是玉帝天庭的“开闭”原则,通过给美猴王封一个“弼马温”的官职,便可使现有系统满足变化了的需求,而不必更改天庭的既有秩序,如下图所示:

    诏安之法的关键便是不允许更改现有的天庭秩序,但允许将妖猴纳入现有秩序中,从而扩展了这一秩序。用面向对象的语言来讲,不允许更改的是系统的抽象层,而允许扩展的是系统的实现层。

抽象化是关键

    解决问题的关键在于抽象化。在像Java语言这样的面向对象的变成语言里面,可以给系统定义出一个一劳永逸、不再更改的抽象设计,此设计允许有无穷无尽的行为在实现层被实现。在Java语言里,可以给出一个或多个抽象Java类或Java接口,规定出所有的具体类必须提供的方法的特征作为系统设计的抽象层。这个抽象层预见了所有的可能扩展,因此在任何扩展情况下都不会改变。这就使得系统的抽象层不需修改,从而满足了“开闭”原则的第二条:对修改关闭。

    同时,由于从抽象层导出了一个或多个新的具体类可以改变系统的行为,因此系统的设计对扩展是开放的,这就满足了“开闭”原则的第一条。

与其他设计原则的关系

    做到“开闭”原则不是一件容易的工作,但是也是有很多规律可循的。这些规律也同样以设计原则的身份出现,但是它们都是“开闭”原则的手段和工具,是附属于“开闭”原则的。

里氏替换原则

    里氏替换原则中说,任何基类可以出现的地方,子类一定可以出现。

    里氏替换原则是对“开闭”原则的补充。正如前面所提到的,实现“开闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体体现,所以里氏替换原则是对实现抽象化的具体步骤的规范。

    一般而言,违反里氏替换原则的,也违背“开闭”原则,反过来并不一定成立。

依赖倒转原则

    依赖倒转原则讲的是,要依赖于抽象,不要依赖于实现。

    看上去依赖倒转原则与“开闭”原则有很大的相似之处,实际上,它们之间的关系是目标和手段之间的关系。“开闭”原则是目标,而达到这一目标的手段是依赖倒转原则。

    换言之,要想实现“开闭”原则,就应当坚持依赖倒转原则。违反依赖倒转原则,就不可能达到“开闭”原则的要求。

合成/聚合复用原则

    要尽量使用合成/聚合,而不是继承关系达到复用的目的。

    显然,合成/聚合复用原则是与里氏替换原则相辅相成的,两者又都是对实现“开闭”原则的具体步骤的规范。前者要求设计师首先考虑合成/聚合关系,后者要求在使用继承关系时,必须确定这个关系时符合一定条件的。

迪米特法则

    一个软件实体应当与尽可能少的其他实体发生相互作用。

    当一个系统面临功能扩展的时候,其中会有一些模块,它们需要修改的压力比其他一些模块要打。最后的结果可能是这些模块需要修改或者不需要修改。但是不论是哪一种情况,如果这些模块是相对孤立的,那么它们就不会将修改的压力传递给其他的模块。

    这就是说,一个遵守迪米特法则设计出来的系统在功能需要扩展时,会相对更容易的做到对修改的关闭。也就是说,迪米特法则是一条通向“开闭”原则的道路。

接口隔离原则

    应当为客户端提供尽可能小的单独的接口,而不要提供大的总接口。

    显然,接口隔离原则与广义的迪米特法则都是一个软件实体与其他的软件实体的通信的限制。广义的迪米特法则要求尽可能限制通信的宽度和深度。接口隔离原则所限制的是通信的宽度,也就是,通信应当尽可能的窄。

    显然,遵循接口隔离原则与迪米特法则会使一个软件系统在功能扩展的过程当中不会讲修改的压力传递到其他的对象。