面向对象的一些设计原则

 

作为一个使用面向对象编程语言的程序员,掌握一些基本的设计原则对提升我们的代码质量是很有帮助的。

首先要介绍的是SOLID原则,它实际是由美国软件工程师罗伯特·C·马丁在21世纪早期引入的针对5个设计原则的首字母缩写。它们分别是单一功能原则(S)、开闭原则(O)、里氏替换原则(L)、接口隔离原则(I)以及依赖反转原则(D)。如果能够在设计中应用这些原则,我们就会得到一个容易进行软件维护和扩展的软件系统。

单一职责原则(Single responsibility principle, SRP)

单一职责原则认为一个对象应该只包含单一的职责,并且该职责被完整地封装在一个类或模块中,即有且仅有一个原因对该它变更。如果一个类负责了两个不同的职责:职责A,职责B。当由于职责A需求发生改变而需要修改它时,则有可能会导致原本运行正常的职责B功能发生故障,也就是说职责A和B被耦合在了一起。因此,单一职责原则认为这两个职责应该分离在不同的类或模块中。把有不同的改变原因的事物耦合在一起的设计是糟糕的。

开闭原则(Open-closed principle, OCP)

开闭原则认为软件中的对象(类,模块,函数等等)应该对扩展是开放的,对修改是封闭的。它的定义听上去比较模糊,关于具体如何实践它并没有说清楚,感觉比较虚,但它是面向对象设计很基础的一个指导性原则,很多其他原则和设计模式其实是为它服务的。当你遵循了其他的设计原则,应用了一些成熟的设计模式,你自然会得到一个符合开闭原则的软件系统。

里氏替换原则(Liskov substitution principle, LSP)

里氏替换原则是由美国计算机科学家芭芭拉·利斯科夫(Barbara Liskov)在1987年首先提出的。它认为一个对象在其出现的任何地方,都可以用其子类的实例做替换,并且不会导致程序的错误。也就是说,子类只可以扩展父类的功能,但不能改变父类原有的功能。如果非要改变父类原有的功能的话,现在比较通用的做法是,让原来的父类和新的子类都继承于一个更泛化的基类,使用组合等方式替换二者之间的继承关系。

接口隔离原则(Interface-segregation principles, ISP)

接口隔离原则认为客户端不应该被强迫使用对其而言无用的方法或功能。服务方应该将庞大臃肿的接口中的方法进行分组,并拆分为数个小的接口。每个接口服务于一个子模块。这样,客户端就只需要知道它们感兴趣的方法。简单地说,就是使用多个专门的接口比使用单个接口要好很多。

依赖倒置原则(Dependency inversion principle, DIP)

依赖倒置原则是一种特定的解耦形式。它认为高层次的模块不应该依赖低层次的模块,依赖关系应该被颠倒,使得低层次模块依赖于高层次模块的需求抽象。也就是说抽象不应该依赖于细节,而是细节应当依赖于抽象。衍生出来其实就是我们所说的面向接口编程和面向抽象编程。

SOLID原则为类和模块的设计提供了一些指导性的意见。接下去要介绍的这个原则我认为不仅可以指导我们进行类和模块的设计,在实现类的内部逻辑时也是很有用的。

迪米特法则(Law of Demeter, LOD)

迪米特法则又被称为最小知识原则。它说的是一个对象应该对其他对象保持最少的了解。通俗的讲,就是一个类对自己依赖的类知道的越少越好。无论被依赖的类的内部逻辑多么复杂,都应该被封闭在其内部,依赖方不应知晓。因为它们之间的关系越密切,耦合度越大,当一方发生改变时,对另一个方的影响也越大。

在实际的开发工作中,一个类所应该依赖的就是被调用的另一个类的方法签名。方法签名应该能够准确的描述其功能,但不应暴露其实现细节。再往上层说其实也就是面向接口编程和面向抽象编程。

虽然法则的定义本身只提到了对象和对象之间的关系,但一个类内部,各个方法之前也会存在一些调用关系。有的调用仅仅通过参数和返回值来进行交互,有的依赖了类的成员变量。其实这些方法间的依赖我们也需要遵守迪米特法则。这样对其中一个方法的实现方式进行变更不会影响到它的调用方。对于一些功能复杂的类来说,这是一种可以让开发者避免牵一发而动全身的方法。

最后简单总结一下,上面这六个原则在面向对象的程序设计中都是比较基础和重要的。希望大家能够在日常工作中注意领会和实践,把架构和代码越做越好。