《设计模式之禅》之——六大设计原则解读
一、单一职责原则(Single Responsibility Principle,SRP)
1. 单一职责原则定义
应该有且仅有一个原因引起类的变更。
2. 单一职责原则好处
- 类的复杂性降低,实现什么职责都有清晰明确的定义;
- 可读性提高,复杂性降低,那当然可读性提高了;
- 可维护性提高,可读性提高,那当然更容易维护了;
- 变更引起的风险降低,变更是必不可少的,如果接口的单一职责做得好,一个接口修改只对相应的实现类有影响,对其他的接口无影响,这对系统的扩展性、维护性都有非常大的帮助。
3. 单一职责适用于接口、类,同时也适用于方法
对于接口,我们在设计的时候一定要做到单一,但是对于实现类就需要多方面考虑了。生搬硬套单一职责原则会引起类的剧增,给维护带来非常多的麻烦,而且过分细分类的职责也会人为地增加系统的复杂性。本来一个类可以实现的行为硬要拆成两个类,然后再使用聚合或组合的方式耦合在一起,人为制造了系统的复杂性。所以原则是死的,人是活的,这句话很有道理。
类的单一职责确实受非常多因素的制约,纯理论地来讲,这个原则是非常优秀的,但是现实有现实的难处,你必须去考虑项目工期、成本、人员技术水平、硬件情况、网络情况甚至有时候还要考虑政府政策、垄断协议等因素。
对于单一职责原则,我的建议是接口一定要做到单一职责,类的设计尽量做到只有一个原因引起变化。
二、里氏替换原则(Liskov Substitution Principle,LSP)
引入里氏替换原则来减少面向对象的语言 “继承” 的缺点
1. 继承的缺点
继承是侵入性的。只要继承,就必须拥有父类的所有属性和方法;
降低代码的灵活性。子类必须拥有父类的属性和方法,让子类自由的世界中多了些约束;
增强了耦合性。当父类的常量、变量和方法被修改时,必需要考虑子类的修改,而且在缺乏规范的环境下,这种修改可能带来非常糟糕的结果——大片的代码需要重构。
2. 里氏替换原则定义
所有引用基类的地方必须能透明地使用其子类的对象。
2.1 包含四层含义
- 子类必须完全实现父类的方法
- 子类可以有自己的个性
- 覆盖或实现父类的方法时输入参数可以被放大
- 覆写或实现父类的方法时输出结果可以被缩小
采用里氏替换原则的目的就是增强程序的健壮性,版本升级时也可以保持非常好的兼容性。即使增加子类,原有的子类还可以继续运行。
3. 项目应用
在实际项目中,每个子类对应不同的业务含义,使用父类作为参数,传递不同的子类完成不同的业务逻辑。
在项目中,采用里氏替换原则时,尽量避免子类的“个性”,一旦子类有“个性”,这个子类和父类之间的关系就很难调和了,把子类当做父类使用,子类的“个性”被抹杀——委屈了点;把子类单独作为一个业务来使用,则会让代码间的耦合关系变得扑朔迷离——缺乏类替换的标准。
三、依赖倒置原则(Dependence Inversion Principle,DIP)
1. 原始定义
- 高层模块不应该依赖低层模块,两者都应该依赖其抽象;
- 抽象不应该依赖细节;
- 细节应该依赖抽象。
*名词解释:
每一个逻辑的实现都是由原子逻辑组成的,不可分割的原子逻辑就是低层模块,原子逻辑的再组装就是高层模块。
在Java语言中,抽象就是指接口或抽象类,两者都是不能直接被实例化的;细节就是实现类,实现接口或继承抽象类而产生的类就是细节,其特点就是可以直接被实例化,也就是可以加上一个关键字new产生一个对象。
2. 在Java语言中的表现
- 模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的;
- 接口或抽象类不依赖于实现类;
- 实现类依赖接口或抽象类。
3. 依赖倒置原则好处
依赖倒置原则的本质就是通过抽象(接口或抽象类)使各个类或模块的实现彼此独立,不互相影响,实现模块间的松耦合。
采用依赖倒置原则可以减少类间的耦合性,提高系统的稳定性,降低并行开发引起的风险,提高代码的可读性和可维护性。
两个类之间有依赖关系,只要制定出两者之间的接口(或抽象类)就可以独立开发了,而且项目之间的单元测试也可以独立地运行,而TDD(Test-Driven Development,测试驱动开发)开发模式就是依赖倒置原则的最高级应用。
4. 依赖的三种写法
- 构造函数传递依赖对象
- Setter方法传递依赖对象
- 接口声明依赖对象(接口注入)
5. 项目应用
需要遵循以下的几个规则
- 每个类尽量都有接口或抽象类,或者抽象类和接口两者都具备
- 变量的表面类型尽量是接口或者是抽象类
- 任何类都不应该从具体类派生
- 尽量不要覆写基类的方法
- 结合里氏替换原则使用(接口负责定义public属性和方法,并且声明与其他对象的依赖关系,抽象类负责公共构造部分的实现,实现类准确的实现业务逻辑,同时在适当的时候对父类进行细化。)
在项目中,大家只要记住是“面向接口编程”就基本上抓住了依赖倒置原则的核心。
四、接口隔离原则(Interface Segregation Principle)
1. 接口隔离原则的定义
- 客户端不应该依赖它不需要的接口;
- 类间的依赖关系应该建立在最小的接口上。
即:建立单一接口,不要建立臃肿庞大的接口。再通俗一点讲:接口尽量细化,同时接口中的方法尽量少。(防止封装过度)
1.1 接口隔离原则包含四层含义
- 接口要尽量小(根据接口隔离原则拆分接口时,首先必须满足单一职责原则。)
- 接口要高内聚(高内聚就是提高接口、类、模块的处理能力,减少对外的交互。)
- 定制服务(采用定制服务就必然有一个要求:只提供访问者需要的方法)
- 接口设计是有限度的(接口的设计粒度越小,系统越灵活,这是不争的事实。但是,灵活的同时也带来了结构的复杂化,开发难度增加,可维护性降低,)
2. 接口隔离原则优点
接口是我们设计时对外提供的契约,通过分散定义多个接口,可以预防未来变更的扩散,提高系统的灵活性和可维护性。
五、迪米特法则(Law of Demeter,LoD)
1. 迪米特法则定义
一个对象应该对其他对象有最少的了解
1.1 迪米特法则包含四层含义
- 只和朋友交流(类与类之间的关系是建立在类间的,而不是方法间,因此一个方法尽量不引入一个类中不存在的对象,当然,JDK API提供的类除外)
- 朋友间也是有距离的(为了保持朋友类间的距离,在设计时需要反复衡量:是否还可以再减少public方法和属性,是否可以修改为private、package-private、protected等访问权限,是否可以加上final关键字等)
- 是自己的就是自己的(如果一个方法放在本类中,即不增加类间关系,也对本类不产生负面影响,就放置在本类中。)
- 谨慎使用Serializable(防止对象实现Serializable接口后属性访问权限的扩大在客户端与服务器端的不一致)
2. 迪米特法则使用
迪米特法则的核心观念就是类间解耦,弱耦合,只有弱耦合了以后,类的复用率才可以提高。其要求的结果就是产生了大量的中转或跳转类,导致系统的复杂性提高,同时也为维护带来了难度。读者在采用迪米特法则时需要反复权衡,既做到让结构清晰,又做到高内聚低耦合。
六、开闭原则(Open Closed Principle)
1. 开闭原则定义
一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。
2. 开闭原则好处
- 开闭原则可以提高复用性
- 开闭原则可以提高可维护性
3. 使用开闭原则
抽象约束(包含三层含义:第一,通过接口或抽象类约束扩展,对扩展进行边界限定,不允许出现在接口或抽象类中不存在的public方法;第二,参数类型、引用对象尽量使用接口或者抽象类,而不是实现类;第三,抽象层尽量保持稳定,一旦确定即不允许修改。)
元数据(配置参数)控制模块行为(通过扩展一个子类,修改配置文件,完成业务变化)
制定项目章程
封装变化(包含两层含义:第一,将相同的变化封装到一个接口或抽象类中;第二,将不同的变化封装到不同的接口或抽象类中,不应该有两个不同的变化出现在同一个接口或抽象类中。)
开闭原则是一个终极目标,任何人包括大师级人物都无法百分之百做到,但朝这个方向努力,可以非常显著地改善一个系统的架构,真正做到“拥抱变化”。