七个设计基本原则:
| 缩写 | 英文名 | 中文名 | 中文名 |
|---|---|---|---|
| SRP | Single Responsibility Principle | 单一职责原则 | 每一个类应该专注于做一件事情 |
| OCP | Open Close Principle | 开闭原则 | 面向扩展开放,面向修改关闭 |
| LSP | Liskov Substitution Principle | 里氏替换原则 | 实现尽量依赖抽象,不依赖具体实现 |
| ISP | Interface Segregation Principle | 接口分离原则 | 应当为客户端提供尽可能小的单独的接口,而不是提供大的总的接口 |
| DIP | Dependency Inversion Principle | 依赖倒置原则 | 实现尽量依赖抽象,不依赖具体实现 |
| LoD | Law of Demeter ( Least Knowledge Principle) | 迪米特法则(最少知道原则) | 又叫最少知识原则,一个软件实体应当尽可能少的与其他实体发生相互作用 |
| CARP | Composite/Aggregate Reuse Principle CARP | 合成/聚合复用原则 | 尽量使用合成/聚合达到复用,尽量少用继承。原则: 一个类中有另一个类的对象 |
注意,通常所说的 SOLID(上方表格缩写的首字母,从上到下)设计原则没有包含本篇介绍的迪米特法则,而只有其他五项
原则一:开闭原则
一个软件实体如类、模块和函数应该对扩展开放,对修改关闭
定义解读
- 用抽象构建框架,用实现扩展细节
- 不以改动原有类的方式来实现新需求,而是应该以实现事先抽象出来的接口(或具体类继承抽象类)的方式来实现
优点
实践开闭原则的优点在于可以在不改动原有代码的前提下给程序扩展功能。增加了程序的可扩展性,同时也降低了程序的维护成本。
如何实践
为了更好地实践开闭原则,在设计之初就要想清楚在该场景里哪些数据(或行为)是一定不变(或很难再改变)的,哪些是很容易变动的。将后者抽象成接口或抽象方法,以便于在将来通过创造具体的实现应对不同的需求。
原则二:单一职责原则
一个类只允许有一个职责,即只有一个导致该类变更的原因
定义解读
- 类职责的变化往往就是导致类变化的原因:也就是说如果一个类具有多种职责,就会有多种导致这个类变化的原因,从而导致这个类的维护变得困难
- 往往在软件开发中随着需求的不断增加,可能会给原来的类添加一些本来不属于它的一些职责,从而违反了单一职责原则。如果我们发现当前类的职责不仅仅有一个,就应该将本来不属于该类真正的职责分离出去
- 不仅仅是类,函数(方法)也要遵循单一职责原则,即:一个函数(方法)只做一件事情。如果发现一个函数(方法)里面有不同的任务,则需要将不同的任务以另一个函数(方法)的形式分离出去
优点
如果类与方法的职责划分得很清晰,不但可以提高代码的可读性,更实际性地更降低了程序出错的风险,因为清晰的代码会让 bug 无处藏身,也有利于 bug 的追踪,也就是降低了程序的维护成本
如何实践
在实际开发中,我们很容易会将不同的责任揉在一起,这点还是需要开发者注意的
原则三:依赖倒置原则
- 依赖抽象,而不是依赖实现
- 抽象不应该依赖细节,细节应该依赖抽象
- 高层模块不能依赖底层模块,二者都应该依赖抽象
定义解读
- 针对接口编程,而不是针对实现编程
- 尽量不要从具体的类派生,而是以继承抽象类或实现接口来实现
- 关于高层模块与低层模块的划分可以按照决策能力的高低进行划分。业务层自然就处于上层模块,逻辑层和数据层自然就归类为底层
优点
通过抽象来搭建框架,建立类和类的关联,以减少类间的耦合性。而且以抽象搭建的系统要比以具体实现搭建的系统更加稳定,扩展性更高,同时也便于维护
如何实践
今后在处理高低层模块(类)交互的情景时,尽量将二者的依赖通过抽象的方式解除掉,实现方式可以是通过接口也可以是抽象类的方式
原则四:接口分离原则
多个特定的客户端接口要好于一个通用性的总接口
定义解读
- 客户端不应该依赖它不需要实现的接口
- 不建立庞大臃肿的接口,应尽量细化接口,接口中的方法应该尽量少
需要注意的是:接口的粒度也不能太小。如果过小,则会造成接口数量过多,使设计复杂化
优点
避免同一个接口里面包含不同类职责的方法,接口责任划分更加明确,符合高内聚低耦合的思想
如何实践
由于接口方法的设计造成了冗余,会导致设计不符合接口隔离原则。
在设计接口时,尤其是在向现有的接口添加方法时,我们需要仔细斟酌这些方法是否是处理同一类任务的:如果是则可以放在一起;如果不是则需要做拆分
原则五:迪米特法则
一个对象应该对尽可能少的对象有接触,也就是只接触那些真正需要接触的对象
定义解读
迪米特法则也叫做最少知道原则(Least Know Principle), 一个类应该只和它的成员变量,方法的输入,返回参数中的类作交流,而不应该引入其他的类(间接交流
优点
实践迪米特法则可以良好地降低类与类之间的耦合,减少类与类之间的关联程度,让类与类之间的协作更加直接
如何实践
今后在做对象与对象之间交互的设计时,应该极力避免引出中间对象的情况(需要导入其他对象的类):需要什么对象直接返回即可,降低类之间的耦合度
原则六:里氏替换原则
所有引用基类的地方必须能透明地使用其子类的对象,也就是说子类对象可以替换其父类对象,而程序执行效果不变
定义解读
在继承体系中,子类中可以增加自己特有的方法,也可以实现父类的抽象方法,但是不能重写父类的非抽象方法,否则该继承关系就不是一个正确的继承关系。
优点
可以检验继承使用的正确性,约束继承在使用上的泛滥
如何实践
里氏替换原则是对继承关系的一种检验:检验是否真正符合继承关系,以避免继承的滥用。因此,在使用继承之前,需要反复思考和确认该继承关系是否正确,或者当前的继承体系是否还可以支持后续的需求变更,如果无法支持,则需要及时重构,采用更好的方式来设计程序
原则七:合成/聚合复用原则
尽量使用合成/聚合达到复用,尽量少用继承。原则: 一个类中有另一个类的对象
定义解读
使用一些已有的对象,使之成为新对象的一部分,新对象通过委派调用已有对象的方法达到复用其已有功能的目的。也就是,要尽量使用类的合成复用,尽量不要使用继承
优点
组合/聚合复用原则可以使系统更加灵活,类与类之间的耦合度降低,一个类的变化对其他类造成的影响相对较少
如何实践
在使用继承时,需要严格遵循里氏代换原则,有效使用继承会有助于对问题的理解,降低复杂度,而滥用继承反而会增加系统构建和维护的难度以及系统的复杂度,因此需要慎重使用继承复用