设计模式六大原则(SOLID)
S ->Single Responsibility Principle 单一职责原则
应该有且仅有一个原因引起类的变更。
好处:
- 类的复杂性降低,实现什么职责都有清晰明确的定义;
- 可读性提高,复杂性降低,那当然可读性提高了;
- 可维护性提高,可读性提高,那当然更容易维护了;
- 变更引起的风险降低,变更是必不可少的,如果接口的单一职责做得好,一个接口修改只对相应的实现类有影响,对其他的接口无影响,这对系统的扩展性、维护性都有非常大的帮助。
注意: 单一职责原则提出了一个编写程序的标准,用“职责”或“变化原因”来衡量接口或类设计得是否优良,但是“职责”和“变化原因”都是不可度量的,因项目而异,因环境而异。单一职责适用于接口、类,同时也适用于方法。
建议:接口一定要做到单一职责,类的设计尽量做到只有一个原因引起变化。
O ->Open Closed Principle 开闭原则
一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。
软件实体包括
● 项目或软件产品中按照一定的逻辑规则划分的模块
● 抽象和类
● 方法
好处:
- 减轻重新测试的压力:新增加的类,新增加的测试方法,只要保证新增加类是正确的就可以了。
- 开闭原则可以提高复用性:缩小逻辑粒度,直到一个逻辑不可再拆分为止。
- 开闭原则可以提高可维护性
注意: 注意 开闭原则对扩展开放,对修改关闭,并不意味着不做任何修改,低层模块的变更,必然要有高层模块进行耦合,否则就是一个孤立无意义的代码片段。
建议:放弃修改历史的想法,一个项目的基本路径应该是这样的:项目开发、重构、测试、投产、运维,其中的重构可以对原有的设计和代码进行修改,运维尽量减少对原有代码的修改,保持历史代码的纯洁性,提高系统的稳定性。
运用:
抽象约束:通过接口或抽象类可以约束一组可能变化的行为,并且能够实现对扩展开放
- 通过接口或抽象类约束扩展,对扩展进行边界限定,不允许出现在接口或抽象类中不存在的public方法
- 参数类型、引用对象尽量使用接口或者抽象类,而不是实现类
- 抽象层尽量保持稳定,一旦确定即不允许修改
元数据(metadata)控制模块行为,减少重复开发
例如:Spring容器,SpringContext配置文件
制定项目章程: 对项目来说,约定优于配置
封装变化:
- 将相同的变化封装到一个接口或抽象类中;
- 将不同的变化封装到不同的接口或抽象类中,不应该有两个不同的变化出现在同一个接口或抽象类中。
L ->Liskov Substitution Principle 里氏替换原则
所有引用基类的地方必须能透明地使用其子类的对象。
四层含义:
-
子类必须完全实现父类的方法
注意: 如果子类不能完整地实现父类的方法,或者父类的某些方法在子类中已经发生“畸变”,则建议断开父子继承关系,采用依赖、聚集、组合等关系代替继承。 -
子类可以有自己的个性
即有子类出现的地方父类未必就可以出现 -
覆盖或实现父类的方法时输入参数可以被放大
-
覆写或实现父类的方法时输出结果可以被缩小
此处复习重写原则:- 重写方法不能比被重写方法限制有更严格的访问级别
- 参数列表必须与被重写方法的相同
- 返回类型必须与被重写方法的返回类型相同,或是子类(子类的返回值也为父类返回值的子类)
- 重写方法不能抛出新的异常或者比被重写方法声明的检查异常更广的检查异常。但是可以抛出更少,更有限或者不抛出异常
- 不能重写被标识为final的方法
- 如果一个方法不能被继承,则不能重写它。如private方法(注意:继承可以继承父类private方法,只是没办法使用)
继承的优点:
- 代码共享,减少创建类的工作量,每个子类都拥有父类的方法和属性;
- 提高代码的重用性
- 子类可以形似父类,但又异于父类
- 提高代码的可扩展性
比如:扩展接口都是通过继承父类来完成的- 提高产品或项目的开放性
缺点:
- 继承是侵入性的。只要继承,就必须拥有父类的所有属性和方法;
- 子类必须拥有父类的属性和方法,降低代码的灵活性。
- 增强了耦合性。
L ->Law of Demeter(Least Knowledge Principle) 迪米特法则(最少知识原则)
一个类应该对自己需要耦合或调用的类知道得最少
- 只和朋友交流
一个类只和朋友交流,不与陌生类交流,不要出现getA().getB().getC().getD()这种情况(在一种极端的情况下允许出现这种访问,即每一个点号后面的返回类型都相同),类与类之间的关系是建立在类间的,而不是方法间,因此一个方法尽量不引入一个类中不存在的对象,当然,JDK API提供的类除外。 - 朋友间也是有距离的
一个类公开的public属性或方法越多,修改时涉及的面也就越大,变更引起的风险扩散也就越大。为了保持朋友类间的距离,在设计时需要反复衡量:尽量不要对外公布太多的public方法和非静态的public变量,尽量内敛,多使用private、package-private、protected等访问权限。 - 是自己的就是自己的
如果一个方法放在本类中,既不增加类间关系,也对本类不产生负面影响,那就放置在本类中 - 谨慎使用Serializable
I ->Interface Segregation Principle 接口隔离原则
接口尽量细化,同时接口中的方法尽量少。
注意:单一职责的审视角度是不相同的,单一职责要求的是类和接口职责单一,注重的是职责,这是业务逻辑上的划分,而接口隔离原则要求接口的方法尽量少。
接口分为两种:
实例接口(Object Interface),在Java中声明一个类,然后用new关键字产生一个实例,它是对一个类型的事物的描述,这是一种接口。(就是普通类对象)
类接口(Class Interface),Java中经常使用的interface关键字定义的接口。
四层含义:
- 接口要尽量小
根据接口隔离原则拆分接口时,首先必须满足单一职责原则 - 接口要高内聚
要求在接口中尽量少公布public方法 - 定制服务
定制服务就是单独为一个个体提供优良的服务。比如:应对不同的访问提供不同的接口 - 接口设计是有限度的
灵活的同时也带来了结构的复杂化,开发难度增加,可维护性降低
运用:
一个接口只服务于一个子模块或业务逻辑
通过业务逻辑压缩接口中的public方法
已经被污染了的接口,尽量去修改,若变更的风险较大,则采用适配器模式进行转化处理
了解环境,拒绝盲从。
D ->Dependence Inversion Principle 依赖倒置原则(面向接口编程)
模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的;
接口或抽象类不依赖于实现类;
实现类依赖接口或抽象类。
好处:可以减少类间的耦合性,提高系统的稳定性,降低并行开发引起的风险,提高代码的可读性和可维护性。
三种写法:
- 构造函数传递依赖对象
- Setter方法传递依赖对象
- 接口声明依赖对象
运用:
- 每个类尽量都有接口或抽象类,或者抽象类和接口两者都具备
- 变量的表面类型尽量是接口或者是抽象类
- 任何类都不应该从具体类派生(只要不超过两层的继承都是可以忍受的,考虑项目维护的成本)
- 尽量不要覆写基类的方法
- 结合里氏替换原则使用