开闭原则
定义
一个软件的实体如类、模块或者函数,应该对扩展开放,对修改关闭。强调用抽象扩展框架,用实现扩展细节。
优点
提高系统的可复用性和可维护性。
核心思想
- 面向抽象编程。
- 需要从业务场景中抽象出业务模型,依赖多态和继承实现不同的功能。
- 它是所有原则中的基础,包括设计模式。
举个栗子
比如一类业务叫课程业务,有输出课程 id,输出课程name 和输出课程价格的基本功能,Java 课程的具体实现即实现此接口并实现具体方法。此时若要进行打折,只需要继承 Java 课程类实现打折信息和原价信息即可,这时并不需要动底层 Java课程的任何信息,只需要扩展打折信息,在业务调用层面修改注入的类即可。如图:依赖倒置原则
定义
高层模块不应该依赖于低层模块,两者都应该依赖于其抽象。抽象不应该依赖细节,细节应该依赖抽象。
优点
通过抽象,可以使各个接口或者实现类彼此独立,降低耦合,提高系统稳定性,提高代码可读性和可维护性,降低程序修改带来的风险。
核心思想
- 针对接口编程,不要针对实现编程。
- 尽量每个类都继承自接口或者抽象类。
- 尽量避免从具体的类派生。
- 尽量不要覆盖基类方法。
举个栗子
学院 Berg 需要学习 Java、Python 课程,将来还要学习 Golang 课程和其他课程,每种课程的学习形式都不一样,那如何保证 Berg 在学习新的课程时能通过扩展增加学习内容但是不对底层做较大改动呢?
首先学习的业务是固定的,可以抽象出来,具体每种课程如何学习,交由具体的课程去实现。
其次 Berg 的学习过程是不变的,只是根据每种课程的学习方式产生变化,即我们只关心 Berg 学习的什么,具体学习已交由课程老师安排。
那这样就如下图所示,课程抽象出一个模型,具体课程的学习过程每个课程类具体实现。学员 Berg 只负责学习,具体学习内容根据课程不同的学习方法定,同时给出课程注入的方式,这样学院 Berg 的学习不依赖于具体的课程。业务输出时只关心给学院学什么课程,输出什么结果,即便再有扩展,也只需要扩展课程类型和最上层业务的输入即可。
单一职责原则
定义
不要存在多余一个导致类改变的原因。
核心
- 一个类、一个接口、一个方法只负责一项职责。
- 单一职责原则需要根据项目实际情况根据业务粒度尽量将业务拆分。只能尽可能拆分,具体还要考虑到工期、成本等现实问题。
- 接口和类层面实际实现时,可以将多个业务接口拆分,具体实现时合并实现。
- 方法层面更加简单,拆分方法功能即可。
优点
降低类的复杂度,提高类的可读性,提高系统的可维护性,降低变更引起的风险。
举个栗子
如课程需要进行【课程管理】和【课程信息维护】两个业务,就可以抽象成这两个接口,具体实现类实现此两个接口。
接口隔离原则
定义
用多个专门的接口,而不使用单一的总接口,客户端不应该依赖它不需要的接口。
核心
- 一个类对一个类的依赖应该建立在最小的接口上。
- 建立单一接口,不要建立庞大臃肿的接口。
- 尽量细化接口,接口中的方法尽量少。
- 适度原则,一定要适度,一定不要过于细而提高了系统的复杂度。
优点
符合高内聚、低耦合的特点,使得类有着良好的可读性、可扩展性和可维护性。
举个栗子
这个也不需要多说,有两点需要注意,一是尽量不要让实现类里出现空方法,二是一定要适度,不要因为接口拆分过多而使得系统更加臃肿。
迪米特法则
定义
一个对象对另一个对象应该保持最少的了解,又叫最少知道原则。
核心
- 尽量降低类与类之间的耦合。
- 只和朋友交流,不跟陌生人说话。(出现在成员变量、方法输入输出参数的类称为方法类,而方法内部的类不是朋友类)
- 我对外部引入的类越少越好
优点
降低类之间的耦合。
举个栗子
这个也很简单,直接通过 UML 图就可以看出来,尽量减少业务中非朋友类的比重,即如果 A 类与C类关联不大,但是 B 类和 C 类关联大,可以将 A 类中对 C 类的处理挪到 B 类中,减少关系不大的类的引入。
里氏替换原则
定义
如果每一个类型为 T1 的对象 o1,都有类型为 T2 的对象 o2,使得以类型 T1 定义的程序 P 在所有的对象 o1 替换为对象 o2 后,程序 P 的行为没有发生任何变化,那么 T2 就是 T1 的子类型。
定义扩展
一个软件实体,如果适用于父类,那么一定适用于子类,所有引用父类的方法必须能透明的使用其子类对象,子类对象能够替换其父类对象,而程序逻辑不变。
核心
- 子类可以扩展父类的功能,但是不能改变父类的功能。
- 子类可以实现父类的抽象方法,但是不能重写父类的非抽象方法。
- 子类可以增加自己的特有方法。
- 重载入参:子类入参应该比父类更宽松,如果父类入参 HashMap,则子类应入参 Map,否则将会被判定为重写父类方法。
- 重载出参:子类的返回值应该比父类更严格,以保证自动封装。
优点
- 可以有效地约束继承泛滥,是开闭原则的一种体现。
- 增强程序健壮性,提高扩展性、兼容性和程序可维护性。
- 降低需求变更带来的风险。
举个栗子
开闭原则的例子中,课程打折时逻辑重写了父类获得价格的方法,使得子类、父类调用同一个方法时,产生了不同的结果,因此是不符合里氏替换原则的。如果将打折方法作为子类的扩展,这样就符合了里氏替换原则。
合成复用原则
定义
软件的复用尽量通过聚合或者组合来实现,而不是单纯的继承。
优点
降低耦合度,使得一个类的变化对另一个类的影响尽可能小。
有疑问加站长微信联系(非本文作者)