设计模式六大原则:
1. 单一职责原则(SRP)
每一个只有一个原因引起类变化,理想情况下是这么设计,但现实因为类分工的标准不一样,导致类功能分离的程度不一样,所以现实中一般是对接口进行单一职责原则的定义
– 将接口的职能单一化,类功能也尽量单一功能话,后续的维护、可读性会增加,不过文件数会增加
2.里氏替换原则(LSP)
看到名字,感觉好高大的样子,也对这个原则的名字充满疑惑。其实原因就是这项原则最早是在1988年,由麻省理工学院的一位姓里的女士(Barbara Liskov)提出来的 。
有二类定义:
1. 如果对每一个类型为 T1的对象 o1,都有类型为 T2 的对象o2,使得以 T1定义的所有程序 P 在所有的对象 o1 都代换成 o2 时,程序 P 的行为没有发生变化,那么类型 T2 是类型 T1 的子类型 (最正宗的定义)
2. 所有引用基类的地方必须能透明地使用其子类的对象
第二个定义是最清晰明确的,通俗点讲,只要父类能出现的地方子类就可以代替父类出现,但是反过来,有子类出现的地方,父类未必能适用。里氏替换原则为良好的继承定义了一个规范,其中包含了4层含义:
-
子类必须完全实现父类的方法
父类接口里申明的方法子类中必要要有对应的实现方法,所以父类出现的地方子类可以替代
-
子类可以有自己的个性
里氏替换原则是可以正着用,即子类可以替换父类,但是不能父类替换子类,因为子类中会有自己的“个性”,即方法和属性
-
覆盖或实现父类的方法时输入参数可以被放大
-
覆盖或实现父类的方法时输入参数可以被缩小
public class Father { public Collection doSomthing(HashMap map){ System.out.println(" Father doSomthing....."); return map.values(); } public Collection searchSomthing(Map map){ System.out.println(" Father searchSomthing....."); return map.values(); } } public class Son extends Father{ //TODO 放大参数范围 --> 这个是属于方法的 Overload public Collection doSomthing(Map map) { System.out.println(" Son doSomthing....."); return map.values(); } //TODO 缩小参数的范围 --> 这个是属于方法的 Overload public Collection searchSomthing(HashMap map) { System.out.println(" Son searchSomthing....."); return map.values(); } } public class Client { public static void main(String[] args) { Father father = new Father(); // Father doSomthing..... father.doSomthing(new HashMap()); Son f = new Son(); // 此处调用的时候可以发现2个doSomthing 方法,参数不同 // Father doSomthing..... f.doSomthing(new HashMap()); // Son doSomthing..... f.doSomthing(new ConcurrentHashMap()); // Father searchSomthing..... father.searchSomthing(new HashMap()); // Son searchSomthing..... 比较上面的 2个参数一样 结果不一样 f.searchSomthing(new HashMap()); } }
在项目中,才有里氏替换原则时,尽量避免子类的“个性”,一旦子类有“个性”,这个子类和父类之间的关系就很难调和了,把子类当作父类使用,子类的个性被抹杀——委屈了点;把子类单独作为一个业务来使用,则会让代码间的耦合关系变得扑朔迷离——缺乏类替换的标准。
3. 依赖倒置原则(DIP)
High level moduls should not depend upon level modules.Both should depend upon abstractions.
- 高层模块不应该依赖低层模块,两者都应该依赖其抽象
Abstractions should not depend upon deatils.
- 抽象不应该依赖实现细节
Details should depend upon abstractions.
- 实现细节应该依赖抽象
每一个业务逻辑的实现都是由原子逻辑组成的,不可分割的原子逻辑就是低层模块,而原子逻辑的再组装就是高层模块,而再Java语言中抽象指的是接口或抽象类,细节指的就是实现类,那么依赖倒置再Java中的表现就是:
- 模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生
- 接口或抽象类不依赖于实现细节
- 实现类依赖接口或抽象类
更加精简的定义:“面向接口编程 OOD”,依赖倒置原则同通过抽象(接口或抽象类)使各个类或模块的实现彼此独立,不互相影响,实现模块间松耦合,在项目中:
- 每个类尽量都有接口或抽象类,或者抽象类和接口都具备
- 变量的表明类型尽量是接口或抽象类
- 任何类都不应该从具体类派生
4.接口隔离原则(ISP)
Java中的接口分为两种:
- 实例接口,Java中声明一个类,然后用new关键字产生一个实例,它是对一个类型事物的描述,这是一种接口。比如 Person zhangSan = new Person(), 这里要求Preson这个类要遵循 ISP 这个原则
- 类接口,Java中interface关键字定义的接口
而隔离的两种定义:
- 客户端不应该依赖它不需要的接口
- 类间的依赖关系应该建立在最小的接口上
总的来说要求
- 接口尽量细化,同时接口中的方法尽量少
- 看似与SRP原则相同,但是两者的关注点并不一样,SRP 侧重的是职责,是根据业务逻辑上的划分,而ISP要求接口中的方法尽量少。比如一个职能接口可能包含了10方法,外部通过文档约束“不使用的方法不要访问”,按照SRP是可以的,但是按照ISP 是不允许的,外部系统需要什么才给什么,接口需要尽可能的细化。
5.迪米特法则(LOD )
迪米特法则也称为最少知识原则,描述的是:一个对象应该对其他对象有最少的了解。通俗地讲,一个类应该对自己需要耦合或调用的类知道得最少,你(被耦合或调用的类)的内部是如何复杂都和我没关系,那是你的事情,我就知道你提供的这么多public方法,我就调用这么多,其他的我不关心。就像包工头从甲方接到了一个任务,甲方只会找包工头问任务进度,不管包工头下一群员工怎么干活。
相对而言一个类公开的public属性或方法越多,修改时涉及的面也越大,变更引起的风险扩散也越大,因此设计的时候需要反复衡量:是否还可以再减少public方法和属性,是否可以修改为private、package-parivate、protected等访问权限,是否可以加上final关键字等。可以坚持一个原则: 如果一个方法放在本类中,即不增加类间关系,也对本类不产生负面影响,就放置再本类中。
6.开闭原则(OCP)
定义:Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification. —> 一个软件实体如类、模块和函数应该对扩展开发,对修改关闭。意思我们应该通过扩展来实现变化,而不是通过修改已有的代码来实现变化,虽是这么说但是难度真大
最后简单的辅助记忆小手段:solid (s-SRP、o-OCP、l-LSP/LOD、i-ISP、d-DIP)