整理总结自《设计模式之禅》一书
1 单一职责原则
Single Responsibility Principle
是用来规范一个类或者接口的职责应该单一,专注。
定义是: 应该有且仅有一个原因引起类的变更。
单一职责原则有什么好处:
类的复杂性降低, 实现什么职责都有清晰明确的定义;
可读性提高, 复杂性降低, 那当然可读性提高了;
可维护性提高, 可读性提高, 那当然更容易维护了;
变更引起的风险降低, 变更是必不可少的, 如果接口的单一职责做得好, 一个接口修改只对相应的实现类有影响, 对其他的接口无影响, 这对系统的扩展性、 维护性都有非常大的帮助。
实际意义:
应该把用户的信息抽取成一个BO(Business Object, 业务对象) , 把行为抽取成一个Biz(Business Logic, 业务逻辑) ,单一职责原则提出了一个编写程序的标准, 用“职责”或“变化原因”来衡量接口或类设计得是否优良, 但是“职责”和“变化原因”都是不可度量的, 因项目而异, 因环境而异对于单一职责原则, 我的建议是接口一定要做到单一职责, 类的设计尽量做到只有一个原因引起变化
2 里氏替换原则
Liskov Substitution Principle
用来强调子类和父类的关系的。
定义1:
如果对每一个类型为S的对象o1, 都有类型为T的对象o2, 使得以T定义的所有程序P在所有的对象o1都代换成o2时, 程序P的行为没有发生变化, 那么类型S是类型T的子类型。
定义2:
所有引用基类的地方必须能透明地使用其子类的对象。
通俗点讲, 只要父类能出现的地方子类就可以出现, 而且替换为子类也不会产生任何错误或异常, 使用者可能根本就不需要知道是父类还是子类。 但是, 反过来就不行了, 有子类出现的地方, 父类未必就能适应注意 如果子类不能完整地实现父类的方法, 或者父类的某些方法在子类中已经发生“畸变”, 则建议断开父子继承关系, 采用依赖、 聚集、 组合等关系代替继承。
子类在没有覆写父类的方法的前提下, 子类方法被执行了, 这会引起业务逻辑混乱, 因为在实际应用中父类一般都是抽象类, 子类是实现类, 你传递一个这样的实现类就会“歪曲”了父类的意图, 引起一堆意想不到的业务逻辑混乱, 所以子类中方法的前置条件必须与超类中被覆写的方法的前置条件相同或者更宽松。
实际意义:
父类的一个方法的返回值是一个类型T, 子类的相同方法(重载或覆写) 的返回值为S, 那么里氏替换原则就要求S必须小于等于T, 也就是说, 要么S和T是同一个类型, 要么S是T的子类采用里氏替换原则的目的就是增强程序的健壮性, 版本升级时也可以保持非常好的兼容性。 即使增加子类, 原有的子类还可以继续运行。 在实际项目中, 每个子类对应不同的业务含义, 使用父类作为参数, 传递不同的子类完成不同的业务逻辑, 非常完美!
3 依赖倒置原则
Dependence Inversion Principle
规范的是类的模块设计,如何设计类与类之间的关系
定义:
依赖倒置原则(Dependence Inversion Principle,DIP) 这个名字看着有点别扭, “依赖”还“倒置”, 这到底是什么意思? 依赖倒置原则的原始定义是:High level modules should not depend upon low level modules.Both should depend uponabstractions.Abstractions should not depend upon details.Details should depend upon abstractions.翻译过来, 包含三层含义:
高层模块不应该依赖低层模块, 两者都应该依赖其抽象; 抽象不应该依赖细节;
细节应该依赖抽象。高层模块和低层模块容易理解, 每一个逻辑的实现都是由原子逻辑组成的, 不可分割的原子逻辑就是低层模块, 原子逻辑的再组装就是高层模块。 那什么是抽象? 什么又是细节呢? 在Java语言中, 抽象就是指接口或抽象类, 两者都是不能直接被实例化的;
细节就是实现类, 实现接口或继承抽象类而产生的类就是细节, 其特点就是可以直接被实例化, 也就是可以加上一个关键字new产生一个对象。
依赖倒置原则在Java语言中的表现就是:
模块间的依赖通过抽象发生, 实现类之间不发生直接的依赖关系, 其依赖关系是通过接口或抽象类产生的;
接口或抽象类不依赖于实现类;
实现类依赖接口或抽象类。
更加精简的定义就是“面向接口编程”——OOD(Object-Oriented Design, 面向对象设计) 的精髓之一。在Java中, 只要定义变量就必然要有类型, 一个变量可以有两种类型: 表面类型和实际类型, 表面类型是在定义的时候赋予的类型, 实际类型是对象的类型, 如zhangSan的表面类型是IDriver, 实际类型是Driver。依赖是可以传递的, A对象依赖B对象, B又依赖C, C又依赖D……生生不息, 依赖不止, 记住一点: 只要做到抽象依赖, 即使是多层的依赖传递也无所畏惧!
对象的依赖关系有三种方式来传递, 如下所示。
1.构造函数传递依赖对象
2.Setter方法传递依赖对象
3.接口声明依赖对象
如何遵循这个原则:只要遵循以下的几个规则就可以:
每个类尽量都有接口或抽象类, 或者抽象类和接口两者都具备这是依赖倒置的基本要求, 接口和抽象类都是属于抽象的, 有了抽象才可能依赖倒置。 变量的表面类型尽量是接口或者是抽象类。
很多书上说变量的类型一定要是接口或者是抽象类, 这个有点绝对化了, 比如一个工具类, xxxUtils一般是不需要接口或是抽象类的。 还有, 如果你要使用类的clone方法, 就必须使用实现类, 这个是JDK提供的一个规范。 任何类都不应该从具体类派生如果一个项目处于开发状态, 确实不应该有从具体类派生出子类的情况, 但这也不是绝对的, 因为人都是会犯错误的, 有时设计缺陷是在所难免的, 因此只要不超过两层的继承都是可以忍受的。 特别是负责项目维护的同志, 基本上可以不考虑这个规则, 为什么? 维护工作基本上都是进行扩展开发, 修复行为, 通过一个继承关系, 覆写一个方法就可以修正一个很大的Bug, 何必去继承最高的基类呢? (当然这种情况尽量发生在不甚了解父类或者无法获得父类代码的情况下。 ) 尽量不要覆写基类的方法如果基类是一个抽象类, 而且这个方法已经实现了, 子类尽量不要覆写。
类间依赖的是抽象, 覆写了抽象方法, 对依赖的稳定性会产生一定的影响。 结合里氏替换原则使用根据里氏替换原则, 父类出现的地方子类就能出现, 我们可以得出这样一个通俗的规则: 接口负责定义public属性和方法, 并且声明与其他对象的依赖关系, 抽象类负责公共构造部分的实现, 实现类准确的实现业务逻辑, 同时在适当的时候对父类进行细化。“倒置”不是倒过来,依赖正置就是类间的依赖是实实在在的实现类间的依赖, 也就是面向实现编程。倒置,就是面对具体的问题时,抽象出一个抽象的层,即接口和抽象类,依赖倒置,就是依赖这些接口和抽象类
4 接口隔离原则
Interface Segregation Principle
规范的是类或接口的结构, 强调接口的方法应该尽可能的少
定义:
Clients should not be forced to depend upon interfaces that they don't use.(客户端不应该依赖它不需要的接口。 ) The dependency of one class to another one should depend on the smallest possible interface.(类间的依赖关系应该建立在最小的接口上。 )把这两个定义概括为一句话: 建立单一接口, 不要建立臃肿庞大的接口。 再通俗一点讲: 接口尽量细化, 同时接口中的方法尽量少。
接口隔离原则是对接口进行规范约束, 其包含以下4层含义:
接口要尽量小。这是接口隔离原则的核心定义, 不出现臃肿的接口(Fat Interface) , 但是“小”是有限度的, 首先就是不能违反单一职责原则,根据接口隔离原则拆分接口时, 首先必须满足单一职责原则。
接口要高内聚什么。是高内聚? 高内聚就是提高接口、 类、 模块的处理能力, 减少对外的交互。 比如你告诉下属“到奥巴马的办公室偷一个×××文件”, 然后听到下属用坚定的口吻回答你: “是, 保证完成任务! ”一个月后, 你的下属还真的把×××文件放到你的办公桌上了, 这种不讲任何条件、 立刻完成任务的行为就是高内聚的表现。 具体到接口隔离原则就是, 要求在接口中尽量少公布public方法, 接口是对外的承诺, 承诺越少对系统的开发越有利, 变更的风险也就越少, 同时也有利于降低成本。
接口设计是有限度的。接口的设计粒度越小, 系统越灵活, 这是不争的事实。 但是, 灵活的同时也带来了结构的复杂化, 开发难度增加, 可维护性降低, 这不是一个项目或产品所期望看到的, 所以接口设计一定要注意适度。
实际意义:
接口隔离原则是对接口的定义, 同时也是对类的定义, 接口和类尽量使用原子接口或原子类来组装。 但是, 这个原子该怎么划分是设计模式中的一大难题, 在实践中可以根据以下几个规则来衡量: 一个接口只服务于一个子模块或业务逻辑; 通过业务逻辑压缩接口中的public方法, 接口时常去回顾, 尽量让接口达到“满身筋骨肉”, 而不是“肥嘟嘟”的一大堆方法; 已经被污染了的接口, 尽量去修改, 若变更的风险较大, 则采用适配器模式进行转化处理; 了解环境, 拒绝盲从。 每个项目或产品都有特定的环境因素。
5 迪米特法则
Law of Demeter
强调的是类间的解耦,,高内聚,低耦合
迪米特法则(Law of Demeter, LoD) 也称为最少知识原则(Least KnowledgePrinciple, LKP) , 虽然名字不同, 但描述的是同一个规则:
一个对象应该对其他对象有最少的了解。
通俗地讲, 一个类应该对自己需要耦合或调用的类知道得最少, 你(被耦合或调用的类) 的内部是如何复杂都和我没关系, 那是你的事情, 我就知道你提供的这么多public方法, 我就调用这么多, 其他的我一概不关心。迪米特法则对类的低耦合提出了明确的要求, 其包含以下4层含义。
1. 只和朋友交流朋友类的定义是这样的: 出现在成员变量、 方法的输入输出参数中的类称为成员朋友类, 而出现在方法体内部的类不属于朋友类。
类与类之间的关系是建立在类间的, 而不是方法间, 因此一个方法尽量不引入一个类中不存在的对象, 当然, JDK API提供的类除外。
2. 朋友间也是有距离的。迪米特法则要求类“羞涩”一点, 尽量不要对外公布太多的public方法和非静态的public变量, 尽量内敛, 多使用private、 package-private、 protected等访问权限。做一个穿长裙的姑娘,绝不穿比基尼
3. 是自己的就是自己的。如果一个方法放在本类中, 既不增加类间关系, 也对本类不产生负面影响, 那就放置在本类中。
4. 谨慎使用Serializable。本地与远程端的修改不同步导致的迪米特法则的核心观念就是类间解耦, 弱耦合, 只有弱耦合了以后, 类的复用率才可以提高。 其要求的结果就是产生了大量的中转或跳转类, 导致系统的复杂性提高, 同时也为维护带来了难度。 读者在采用迪米特法则时需要反复权衡, 既做到让结构清晰, 又做到高内聚低耦合。
6 开闭原则
Open Closed Principle
此模式为设计模式的指导思想
开闭原则是Java世界里最基础的设计原则, 它指导我们如何建立一个稳定的、 灵活的系统, 先来看开闭原则的定义:Software entities like classes,modules and functions should be open for extension but closed formodifications.(一个软件实体如类、 模块和函数应该对扩展开放, 对修改关闭。 )一个软件实体应该通过扩展来实现变化, 而不是通过修改已有的代码来实现变化。
软件实体包括以下几个部分: 项目或软件产品中按照一定的逻辑规则划分的模块。 抽象和类。 方法。
开闭原则对扩展开放, 对修改关闭, 并不意味着不做任何修改, 低层模块的变更, 必然要有高层模块进行耦合, 否则就是一个孤立无意义的代码片段。前面五个原则都是开闭原则的具体形态,也就是说前五个原则就是指导设计的工具和方法, 而开闭原则才是其精神领袖。"Keep the bargreen to keep the code clean"如何实现开闭原则:
1. 抽象约束抽象是对一组事物的通用描述, 没有具体的实现, 也就表示它可以有非常多的可能性,可以跟随需求的变化而变化。
因此, 通过接口或抽象类可以约束一组可能变化的行为, 并且能够实现对扩展开放, 其包含三层含义:
第一, 通过接口或抽象类约束扩展, 对扩展进行边界限定, 不允许出现在接口或抽象类中不存在的public方法;
第二, 参数类型、 引用对象尽量使用接口或者抽象类, 而不是实现类;
第三, 抽象层尽量保持稳定, 一旦确定即不允许修改。
2. 元数据(metadata) 控制模块行为编程是一个很苦很累的活, 那怎么才能减轻我们的压力呢? 答案是尽量使用元数据来控制程序的行为, 减少重复开发。 什么是元数据? 用来描述环境和数据的数据, 通俗地说就是配置参数, 参数可以从文件中获得, 也可以从数据库中获得。
3. 封装变化对变化的封装包含两层含义:
第一, 将相同的变化封装到一个接口或抽象类中;
第二,将不同的变化封装到不同的接口或抽象类中, 不应该有两个不同的变化出现在同一个接口或抽象类中。 封装变化, 也就是受保护的变化(protected variations) , 找出预计有变化或不稳定的点, 我们为这些变化点创建稳定的接口, 准确地讲是封装可能发生的变化, 一旦预测到或“第六感”发觉有变化, 就可以进行封装