设计模式之七大原则
我们为什么要学习设计模式?
1、个人观点
在最近的编码过程中,尤其是在实习的几个月里,我深刻地意识到了设计模式的重要性。写代码,即使是CURD的代码,也需要考虑到整个系统的维护性[可读性、规范性]、扩展性、重用性问题。
当然,一段质量高的代码,不仅仅需要设计模式,也需要 clean code, effective code 等书籍的指导。
2、客观观点
- 面试必要
- 能显著提升代码能力
- 在框架和模块设计中呗大量使用
七大原则
- 单一职责
- 接口隔离
- 依赖倒置
- 里式替换
- 开闭原则
- 迪米特原则
- 合成复用
| 设计原则 | 一句话归纳 | 目的 |
|---|---|---|
| 开闭原则 OCP | 对扩展开放,对修改关闭 | 降低维护带来的新风险 |
| 依赖倒置原则 DIP | 高层不应该依赖低层,要面向接口编程 | 更利于代码结构的升级扩展 |
| 单一职责原则 SRP | 一个类只干一件事,实现类要单一 | 便于理解,提高代码的可读性 |
| 接口隔离原则 ISP | 一个接口只干一件事,接口要精简单一 | 功能解耦,高聚合、低耦合 |
| 迪米特法则 LoD | 不该知道的不要知道,一个类应该保持对其它对象最少的了解,降低耦合度 | 只和朋友交流,不和陌生人说话,减少代码臃肿 |
| 里氏替换原则 LSP | 不要破坏继承体系,子类重写方法功能发生改变,不应该影响父类方法的含义 | 防止继承泛滥 |
| 合成复用原则 CRP | 尽量使用组合或者聚合关系实现代码复用,少使用继承 | 降低代码耦合 |
1、单一职责原则
对于类的维度来说,一个类应该只负责一项职责。如果某个类负责了两个不同的职责,则它应该被拆分为两个类。
举例
设计一个学生工作管理程序,学生工作分为生活辅导和学业辅导。所以,应该让辅导员类负责生活辅导,学生导师负责学业辅导。不应该将两份工作交给一个老师来负责。
注意点
- 只有当类中的方法数量足够少,可以在方法级别保持单一原则
2、接口隔离原则
客户端不应该依赖它不需要的接口,一个类对另一个类的依赖应该建立在最小的接口上。
举例:
该例子中,A类依赖了它不需要的接口,造成了不必要的耦合
解决方法
应该将 Interface1 拆分成几个独立的接口,类A 和 类C 分别与他们需要的接口建立依赖关系
3、依赖倒置原则
1、高层模块不应该依赖低层模块,二者都应该依赖其抽象
2、抽象不应该依赖细节,细节应该依赖抽象
3、依赖倒置的中心思想是 面向接口编程,而不是面向具体细节编程
因为,相对于细节的多边形,抽象的东西要稳定的多
举例
就比如一个支付系统,今天客户要求要能支持微信支付,明天可能就要新增支付宝支付,支付操作的具体细节是多变的。我们通过抽象,把支付操作抽象为一个接口或者抽象方法来保持其稳定,客户端就不需要进行修改,只需要传递想要进行支付的方式的实现类即可。以抽象为基础搭建的架构比细节为基础的框架要稳定的多。
Java 中的抽象与细节:
- 抽象,使用接口和抽象类
- 细节,即实现类
依赖关系的三种传递方式
1、接口传递
将接口作为参数,进行传递
interface IOpenAndClose {
public void open(ITV tv); //抽象方法,接收接口
}
interface ITV { //ITV 接口
public void play();
}
2、构造方法传递
interface IOpenAndClose {
public void open( ); //抽象方法,接收接口
}
interface ITV { //ITV 接口
public void play();
}
class OpenAndClose implements IOpenAndClose {
public ITV tv; //成员
public OpenAndClose(ITV tv) { //构造器
this.tv = tv;
}
public void open() {
this.tv.play();
}
}
3、setter 方法传递
interface IOpenAndClose {
public void open( ); //抽象方法,接收接口
public void setTv(ITV tv);
}
class OpenAndClose implements IOpenAndClose {
private ITV tv;
public void setTv(ITV tv) {
this.tv = tv;
}
public void open() {
this.tv.play();
}
}
注意点
- 变量的声明尽可能是抽象类或接口,这样有益于程序扩展和优化
- 低层模块尽量要有抽象类或接口,这样程序稳定性更好
4、里式替换原则
继承必须确保超类所拥有的性质在子类中仍然成立。
首先,继承包含了一层意思,即凡是父类中已经实现好的方法,虽然父类并不要求所有的子类都必须遵循,但是如果子类对这些已经实现好的方法任意修改,则会对整个继承体系造成破坏。
继承虽然带来了便利,亦带来了侵入性,增加了程序对象之间的耦合性,降低了可移植性。
所以根据里式替换原则:
- 在使用继承时,子类中尽量不要重写父类方法
- 继承实际上使得两个类耦合性增强了,所以尽量使用 聚合、组合、依赖 来代替继承,解决问题
5、开闭原则
一个软件实体,如类、模块和函数应该对扩展开放(对提供方),对修改关闭(对使用方);
当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有代码来实现
举例
该类图展示了一个图形设计,根据type类型来画出不同的图形。但是这个设计违反了开闭原则,如果要新增加一个三角形,不仅要在GrapicEditor类中添加 drawTriangle() 还要 增加 if else 的判断。
解决方法
把创建 Shape 类做成抽象类,并提供一个 抽象的 draw 方法,让子类去实现即可,这样我们有新的图形种类时,只需要让新的图形类继承 Shape,并实现 draw 方法即可使用方的代码就不需要修 -> 满足了开闭原则
6、迪米特法则
迪米特法则(Demeter Principle)又叫 最少知道原则,即一个类 对自己依赖的类知道的越少越好。
简单来说,对于被依赖的类,不管这个类多么复杂,都尽量地将逻辑封装在自己内部。对外只提供 public 方法,不对外泄露任何信息
直接朋友概念
每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。
耦合的方式很多,依赖,关联,组合,聚合等。其中,我们称出现 成员变量、方法参数、方法返回值 中的类为直接的朋友,而出现在**局部变量中的类不是直接的朋友。**也就是说,陌生的类最好不要以局部变量的形式出现在类的内部。
7、合成复用原则
简单来说,能用合成/聚合,就不要用继承
通过构造方法、成员变量直接实例化、set方法赋值等方式来解耦,代替继承