为了使软件有着较高的可维护性以及其他各方面的优质性能,在面向对象的编程过程中,我们需要有着良好的设计方案,在一些基本的编程原则下设计代码。在第五章的学习中,讲到了面向对象编程过程中的五大原则,简称为 SOLID 下面我们依次介绍。
单一责任原则(Single Responsibility Principle, SRP)
SRP指明了,每一个类的职责要单一,即不能存在多个原因引起这个类的变化。通过职责与类的分离,降低系统的耦合度,提升系统内聚度,在维护过程中,尽可能小范围的修改代码。SRP的设计体现如下,我们将数据传输的功能和连接的功能分离为两个接口,在需要的设计中实现对于的接口即可,如下,我们不能将所有的学生工作委托给一个老师,最好将功能分派给对应的老师,高效处理工作:
需要注意的是,在面向对象设计的过程中,不需要过于刻意地使用利用单一责任原则,否则会导致类的数量急剧增加,反而难以维护。我们需要权衡利弊,在必要时使用SRP。
开闭原则(Open/Closed Principle, OCP)
在维护软件的过程中,我们可能会因为扩展新的功能反复修改代码,最终导致的结果就是代码与最初的设计思路背道而驰。因为我们当初的设计是基于当时的需求产生的,面对更多的扩展而反复修改代码,会导致代码的复杂度提高,难以维护。OCP要求我们设计的代码对于扩展开放,对于修改封闭,即面对新的变化时,可以不去修改原有的代码,只需要添加新的代码扩展功能即可。
实现OCP关键的解决方案在于抽象,即对创建的ADT进行抽象,通过继承和委托,使其面向扩展开放。OCP的设计体现如下,我们通过抽象以及继承的方式设计ADT,利用多态性实现某些方法,以参数多态代替了繁琐的if-else语句:
里氏替换原则(Liskov Substitution Principle, LSP)
LSP原则在之前的博客中有了详细的介绍,其内容是指,在所有使用父类型的地方都可以透明的使用其子类型,即子类型能够代替父类型的使用。LSP原则是继承复用的基石,也是提高可维护性时需要注意的一点,一般为了做到LSP原则,需要在设计代码时完成以下几点内容:
1. 子类型必须实现父类型中的抽象方法。
2. 子类型可以添加新的方法,但不可以删除父类型中的方法。
3. 子类型中重写父类型的方法时,需要有着更加宽松的前置条件,更加严格的后置条件,并且抛出更少类型的异常。
接口隔离原则(Interface Segregation Principle, ISP)
ISP要求我们只为客户端提供必要的接口,而不需要使用无用的接口,通过将臃肿的“胖接口”依照其抽象结果分解为多个小颗粒的接口,预防外来变更的扩散,提高系统的灵活性和可维护性,达到高内聚、低耦合的设计目标。也可以防止实现过大接口时需要被迫实现其中的无用方法。
我们可以感受到,接口隔离原则和单一责任原则有着很大的相似之处,两者能提高类的内聚度,降低耦合度,体现封装的思想,但两者之间也有一定的区别:
1. 单一职责原则注重的是职责,而接口隔离原则注重的是对接口依赖的隔离。
2. 单一职责原则主要是约束类,它针对的是程序中的实现和细节;接口隔离原则主要约束接口,主要针对抽象和程序整体框架的构建。
下面这个例子就体现了ISP原则,将Worker类中的两个方法封装在两个接口中,实现了接口隔离的目标:
依赖转置原则(Dependency Inversion Principle, DIP)
即通过面向接口的编程降低类之间的耦合度。我们想象这样一个场景:在很多人共同完成一个大型项目时,复杂底层的同学完成一些方法后,将其提交给负责高层的同学,这样的行为就违反了DIP原则,因为此时在高、底层的关系中,底层设计起了主导作用,两者之间缺少了一定的抽象。DIP原则要求:高层模块不应该依赖于底层模块,两者都应该依赖于抽象,也可以说,抽象不应该依赖于具体,具体应该依赖于抽象。转置在此的含义,即是高、底层之间的转置关系,需要高层提出(抽象出)需求(接口),底层满足(实现)需求。
下图就体现出了DIP原则,高、底层之间通过抽象接口实现依赖关系,降低了系统的耦合度: