OCP的思考

OCP的思考

All problems in computer science can be solved by another level of indirection.

计算机科学中的所有问题都可以通过增加一个间接层来解决。

--- David Wheeler

在编写程序或设计数据库时,总是希望把功能或字段设计得尽可能全面,以便后续可以处理更复杂的需求。比如最初编写了一套针对长方体的检测算法,当需要支持圆柱体或长方组合体时,本能反应是修改原有的检测算法,将其替换为更复杂更通用的算法,或者在原有代码中加入更多的if-else分支。同样地,当最初设计的材料模型数据表在需要支持更复杂的模型时,直觉上最简单的做法也是直接在原有表中添加更多字段。这时就会陷入到一种纠结的困境中:保留原始结构不足以满足新的需求,而对原有结构进行扩充则使代码和数据库变得越来越复杂,难以维护。实际上一旦产生了这种焦虑,就意味着现有的系统设计没有遵循开闭原则(Open-Closed Principle,OCP)。

开闭原则是说,软件实体(类、模块、函数等)应该对扩展开放,对修改封闭。也就是说,在面对新的需求或功能时,应该尽量通过扩展现有功能来实现,而不是修改已有的代码或结构。为了实现这一原则,一个有效的方法是:一旦遇到或者预测到了某种变化,那么就可以尝试设计一个间接层(抽象)来隔离这种变化。

比如在设计几何类时,最初的检测算法只支持长方体。当需要添加其他几何体类型的支持,并且可以预见未来会不断添加更多的几何体类型时,就应该在不同几何体的检测算法这一变化点上增加一个抽象层。可以创建一个几何体的抽象类或接口类,定义所有几何对象必须实现的方法接口(例如碰撞检测、面积和体积计算等),然后让具体的几何对象来实现这些接口。当需要支持新几何体(如圆柱体)时,不需要修改已有的代码,而是扩展一个新的几何类(如Cylinder),实现抽象基类的接口。这种方式使得新功能和已有功能相互独立,避免对现有代码的干扰。同时维持了各种算法特异性的优势,因为过度泛化的算法不仅会降低程序性能,还会增加维护难度。

在数据库设计中,也可以采用类似的思路。比如最初的数据表interface_data只存储基本模型的数据。当需要支持更复杂的模型时,不是直接在现有数据表中添加字段,而是新建一张数据表,专门存储与复杂模型相关的数据。在新建数据表后,可以设计一个中间层,比如某个模型属性表interface_property,用于将不同模型的数据分配到不同的数据表中,或者在原有的模型表中增加一个字段用于指向合适的数据表。

引入抽象层后,程序会更满足单一职责原则(Single Responsibility Principle),并自然逐渐向满足依赖倒置原则(Dependency Inversion Principle,DIP)的方向靠拢:抽象不依赖于细节,细节依赖于抽象。而实际上这些原则或者模式的应用,并不是在最开始就全部设计好的,而是随着系统的需求和演化而逐步形成的。这也是一种设计哲学的转变,从“追求完美的静态的形而上学抽象”逐渐转向更为务实的“响应变化”的思路。一切都是运动的,只有矛盾才会推动系统的新发展。Robert C. Martin在敏捷软件开发里提到过,

...但请注意,团队不是在一开始设计该模块时就试图预测程序将如何变化。相反,他们是以最简单的方法编写的。直到需求最终确实变化时,他们才修改模块的设计,使之对该种变化保持弹性。有人会认为他们仅仅完成了一半的工作。他们在使自己免于不同的输入设备带来的麻烦时,本可以也使自己免于不同的输出设备带来的麻烦。然而,团队实在不知道输出设备是否会变化。现在就添加额外的保护没有任何现实意义。很明显,如果需要这种保护时,以后可以非常容易地添加。因此,实在没有现在就添加的理由。

...敏捷设计是一个过程,不是一个事件。它是一个持续的应用原则,模式以及实践来改进软件的结构和可读性的过程。

...敏捷开发人员不会对一个庞大的系统预先设计应用哪些原则和模式。相反,这些原则和模式被应用在一次次的迭代中,力图使代码以及代码所表达的设计保持干净。

--- 敏捷软件开发 原则、模式与实践

Robert C. Martin.

引入间接层是一种通过增加抽象来处理复杂性和变化的有效方式。在长方体的例子中,引入几何抽象类,将具体几何体的实现(如长方体、圆柱体)与上层逻辑解耦。这一抽象层允许我们在无需修改现有几何体代码的前提下,通过扩展新的几何类型来满足新的需求。在数据库设计中,同样可以引入一个中间层来管理不同模型的数据分配,这能够有效地管理模型和数据的映射关系,减少在未来扩展复杂模型时对现有结构的影响。通过增加抽象层或间接层,可以实现以下目标:

  • 解耦:避免上层逻辑直接依赖底层具体实现,从而增强系统的可维护性和可扩展性。
  • 灵活性:未来的扩展只需在新的层次上实现即可,无需对现有系统进行大规模修改。
  • 实现开闭原则:开闭原则的核心思想是通过扩展而非修改来满足新的需求,引入间接层正是这一思想的具体体现。

软硬件系统中也使用了类似的引入间接层的思想,尤其是在复杂系统中。比如OSI七层模型将通信系统中的数据流划分为七个层次,从最高层的应用数据表示到物理层的数据传输。每个中间层为上一层提供功能,其自身功能则由下一层提供。这种设计确保了每一层的复杂性被隔离,我们只需关注每一层自身的问题,这也是Anderson所说的“More is Different”的体现,否则人类没法理解或构建出任何复杂的系统。此外,分层设计还使得每一层的实现可以在不影响其他层的情况下进行替换。例如,从有线网络转到无线网络时,只需修改物理层和数据链路层的实现,而无需更改传输层或应用层的协议。这样的模块化设计使系统具有高度的可替换性和可维护性。