设计模式的基本原则
@author 无忧少年
@createTime 2019/7/9
1. 单一职责原则
单一职责原则(SRP : Single responsibility principle):就一个类而言,应该仅有一个引起它变化的原因。
单一职责中的职责我理解为是功能的意思,如果说一个类中职责过多,相当于将这些职责耦合在一起,其中一个职责的改变,可能会影响其它职责或者削弱或抑制这个类完成其它职责的能力。这种耦合会导致脆弱的设计,当发生变化时,可能会发生意想不到的意外。
在软件设计过程中,为了防止这样的情况发生,我们应该将一些职责过多的类拆分开来,如果说我们能够想到多余一个动机去改变一个类,那么这个类就具有多余一个的职责,就应该考虑类的职责分离。
但是在软件设计完成之后一个类的原本只有一个职责A,但是之后需求进行了,这个类有两个职责B1,B2,这个时候本应该将这个类拆分成类A1实现职责B1,类A2实现职责B2,但是这样不仅要完全更改原本代码,可能还要更改客户端代码,虽然后这样实现是很好的,但是这样更改的代码太大了。另一种实现方法也是代价比较小的方法就是在类A中分别实现两个方法来实现两个职责B1、B2,这样在方法层面,也是实现了单一职责原则,两个方法也是互相不影响的。
Spring 的面向切面编程也符合单一职责原则,每一层实现不同的功能,将职责分离。提高代码的可读性和系统的可维护性。
单一职责的优点
- 可以降低类的复杂度,一个类只负责一项职责,其逻辑比负责多项职责的逻辑简单的多
- 提高类的可读性,提高系统的可维护性,类的职责单一,其代码的可读性必然会提高,维护也会更方便
- 变更引起的风险降低,变更是必然的,如果单一职责的原则遵守好,当修改一个职责的时候只对当前修改的职责有影响,对其它的职责的影响可以显著降低,风险也就会降低。
2. 开放-封闭原则
开放-封闭原则:是说软件实体(类、模块、函数等等)应该可以扩展,但是不可修改。
开放-封闭原则其实是扩展开放、修改封闭,就是对于设计的一个系统是便于拓展的,在扩展功能时,是不可以修改原有代码,应该将这个功能抽象出来,由其继承的类去实现这个功能。
在我们项目中也是实现了这个功能,就是分页查询这个功能,对于分页查询的方法getDataByPage()是开放的对于分页查询的接口是封闭的不可修改的,扩展的就是不同类的分页方法的具体实现如下图
[外链图片转存失败(img-4dnBMxDs-1565513265419)(图片\开放封闭原则结构图.png)]
在看的《大话设计模式》中的例子是一个计算器的实现对于对于算法的实现应该抽象出来,这样可以很方便的新增一种新的算法,对于客户端对于输入输出的处理应该是封闭的不可以修改的。
开放-封闭原则的优点
- 代码可读性高,可维护性强
- 帮助缩小逻辑粒度,以提高可复用性
- 可以使维护人员只扩展一个类而非修改一个类,从而提高可维护性
- 在设计之初考虑所有可能变化的因素,留下接口,从而符合面向对象开发的要求
3. 依赖倒转原则
依赖倒转原则: 1. 高层模块不应该依赖底层模块。两个都应该依赖抽象
2. 抽象不应该依赖细节。细节应该依赖抽象
这个原则意思就是抽象不应该依赖细节,细节应该依赖抽象,白话说就是针对接口编程,不要对实现编程。
例子还可以说是计算器的例子,在计算器中对于算法的实现,是应该抽象出来的,如果不抽象出来,计算器这个高层模块就会依赖这个算法模块,例子如下
// 计算器类
public class Calculator {
private Add add;
public void setAdd(Add add) {
this.add = add;
}
public Add getAdd() {
return add;
}
}
// 加法类
public class Add {
public double result;
public Add(double a, double b) {
this.result = a + b;
}
}
// 减法类
public class Sub {
public double result;
public Sub(double a, double b) {
this.result = a - b;
}
}
public static void main(String[]args){
Calculator calculator=new Calculator();
calculator.setAdd(new Add(1,2));
System.out.println(calculator.getAdd().result);
calculator.setAdd(new Sub(2,1)); // 报错无法计算
}
此时这个计算器类会依赖这个底层的加法类,这样是不对的,应该是高层和底层都依赖抽象,如下图
[外链图片转存失败(img-qJuzvfez-1565513265420)(图片\依赖倒转原则依赖图.png)]
修改之后的代码如下:
// 计算器类
public class Calculator {
private Algorithm algorithm;
public Algorithm getAlgorithm() {
return algorithm;
}
public void setAlgorithm(Algorithm algorithm) {
this.algorithm = algorithm;
}
}
// 算法抽象类
public abstract class Algorithm {
protected double result;
public double getResult() {
return algorithm;
}
}
// 加法类
public class Add extends Algorithm {
public Add(double a, double b) {
this.result = a + b;
}
}
// 减法类
public class Sub extends Algorithm {
public Sub(double a, double b) {
this.result = a - b;
}
}
public static void main(String[]args){
Calculator calculator=new Calculator();
calculator.setAlgorithm(new Add(1,2));
System.out.println(calculator.getAlgorithm.getResult());
calculator.setAlgorithm(new Sub(2,1));
System.out.println(calculator.getAlgorithm.getResult());
}
这样修改之后计算器类不在依赖具体的算法,只是依赖算法的抽象类,具体的算法也是依赖于算法的抽象类。符合依赖反转原则。
4. 里氏代换原则
里氏代换原则:子类型必须能够替换掉他们的父类型。
它的白话翻译就是一个软件实体如果是一个父类的话,那么一定适用于其子类,而且察觉不出父类对象和子类对象的区别。也就是说,把父类都替换成它的子类,程序的行为没有变化,简单的说子类必须能够替换掉他们的父类型。
其中也体现了开放-封闭原则对于修改封闭,所以子类才能够替换掉他们的父类型,如果对父类进行了修改,则子类和父类中相同的行为可能不一致,也是不符合里氏代换原则。
5.迪米特法则
迪米特法则: 如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中一个类需要调用另外一个类的某一个方法的话们可以通过第三者转发这个调用。
迪米特法则首先强调的前提是在类的结构设计上,每一个类都应当尽量降低成员的访问权限,也就是说,一个类要包装好自己的private属性,不要将这些属性让别的类知道。
迪米特法则主要是强调了类之间的松耦合。