文章目录
绪论
学习方法:
应用场景 > 设计模式 > 剖析原理(画出类图) > 分析实现步骤(图解) > 代码实现 > 框架或者项目源码分析
一、设计模式的七大设计原则
- 单一职业原则
- 接口隔离原则
- 依赖倒转原则
- 里氏替换原则
- 开闭原则
- 迪米特法则
- 合成复用原则
1. 单一职业原则
定义:一个类只负责一项原则,如果类A负责职责1和职责2,那么当职责1因其需求变更而改变A类的时候,可能会造成指责2执行错误。因此需要将A类按照不同的职责,拆分成A1和A2两个类。
a. 单一职责原则的注意事项和细节:
(1)降低类的复杂度,一个类只负责一项职责
(2)提高类的可读性和可维护性
(3)降低类中方法的变更引起的风险
(4)通常情况下我们需要遵守单一职责原则, 只有在逻辑足够简单,才可以在代码级违反单一职责原则;只有在类中方法足够少的时 候,才可以在方法级别保持单一职责原则。
2. 接口隔离原则
定义:客户端不应该依赖它不需要的接口,即一个类对另一个类的依赖应该建立在最小的接口上。
a. 传统方法存在的问题和接口隔离原则对其的改进:
(1)传统方法中:类A通过接口Interface1依赖于B,类C通过Interface1依赖于类D,如果接口Interface1对于类A和类C来说不是最小接 口,那么类B和类D就必须实现Interface1的全部方法(而其中的部分方法是类B和类D不需要的)。
比如说,A只会依赖于B使用Interface1的1、2、3三个方法,而C只会依赖于D使用Interface1的1、4、5三个方法。那么B和D就 不需要实现Interface1的全部方法,只需要实现其一部分的方法即可!
(2)在传统方法中的改进:
将接口Interface1拆分成独立的几个接口,类A和类C分别与他们需要的接口建立依赖关系,也就是采用接口的隔离原则。
将Interface1中出现的方法,根据实际的情况,将其拆分成为三个接口,拆分后的结构如下:
3. 依赖倒置原则
定义:
1)高层模块不依赖于底层模块,两者都依赖于其抽象;
2)抽象不依赖于细节,而细节要依赖于抽象;
3)该原则的中心思想即面向接口编程。
4)依赖倒置原则的设计理念:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建的架构比以细节为基础搭建的架构要稳定的多。在Java中,抽象指的是接口和抽象类,细节就是具体的实现类。
5)使用接口和抽象类的目的是制定规范,而不涉及任何具体的操作,把展现细节的任务交给它们的实现类去完成。
a. 依赖关系传递的三种方式:
(1)接口传递
(2)构造器传递
(3)setter方法传递
代码示例:
/**
* @Author: ZhangLingRan
* @Description: 依赖倒置的三种依赖关系传递方法
* @DateTime: 2021/11/9 9:47
*/
public class ThreePropMethod {
public static void main(String[] args) {
IReceiver1 receiver = new Email1();
// 方式1: 通过接口来传递依赖关系
Person1 person1 = new Person1();
person1.receive(receiver);
// 方式2: 通过构造器来传递依赖关系
Person2 person2 = new Person2(receiver);
person2.receive();
// 方式3: 通过接口来传递依赖关系
Person3 person3 = new Person3();
person3.setter(receiver);
person3.receive();
}
}
/**
* 方式1: 通过接口来传递依赖关系
*/
class Person1 {
public void receive(IReceiver1 receiver) {
System.out.println(receiver.getInfo());
}
}
/**
* 方式2: 通过构造器传递依赖关系
*/
class Person2 {
private IReceiver1 receiver;
public Person2(IReceiver1 receiver) {
this.receiver = receiver;
}
public void receive() {
System.out.println(this.receiver.getInfo());
}
}
/**
* 方式3: 通过构造器传递依赖关系【使用方】
*/
class Person3 {
private IReceiver1 receiver;
public void setter(IReceiver1 receiver) {
this.receiver = receiver;
}
public void receive() {
System.out.println(this.receiver.getInfo());
}
}
/**
* 接收器接口【这里就是提供方】
*/
interface IReceiver1 {
/**
* 接收器返回消息
* @return
*/
String getInfo();
}
/**
* 电子邮件
*/
class Email1 implements IReceiver1 {
@Override
public String getInfo() {
return "接收到了电子邮件信息...";
}
}
b. 依赖倒置原则的注意事项和细节
(1)低层模块要尽量有抽象类或接口,或两者都有,程序的稳定性更好
(2)变量的声明类型尽量是抽象类或者是接口,这样变量引用和实际对象间就会存在一个缓冲层,利于程序的扩展和优化。
(3)继承时遵循里氏替换原则
4. 里氏替换原则
a. 关于继承的一些思考
- 继承的定义:在父类中实现的方法,实际上是在设定规范和契约,虽然它不要求子类必须遵循这些契约,但如果子类对这些已经实现好的方法进行任意的修改,就会对整个继承体系造成破坏。
- 继承带来一定便利的同时,也会带来弊端:继承会给程序带来侵入性,降低程序的可移植性,增加对象间的耦合度,如果一个类被其他类继承,则当该类修改的时候,必须要考虑到所有的子类,且父类修改之后,所有涉及到的子类功能都可能会发生故障。
- 在编程过程中,该如何正确的使用继承?==> 需要遵循里氏替换原则;
b. 里氏替换原则的定义:
- 如果对每个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的程序P在所有的对象o1替换成o2时,程序P的行为没有发生变化,那么类型T2是类型T1的子类型。 换句话说:所有引用基类的地方必须能够透明的时候其子类!
- 在使用继承的时候,子类中尽量不要去重写父类的方法(做到这一点,其实就遵守了里氏替换原则了)
- 里氏替换原则告诉我们,遵循里氏替换原则,继承实际上让两个类的耦合度增强了。在适当的情况下,如果一定要在子类中使用父类的方法,可以将原有的继承关系去掉,将原来的子类和父类的公共方法提取并抽象成为一个基类(Base),并使原来的子类和父类共同继承该基类,另外也可以通过聚合、组合、依赖来解决该问题。
5. 开闭原则
定义:
1)一个软件实体(类、模块、函数)应该对扩展是开放的(对提供方而言),对修改是关系的(对使用方而言)。用抽象来构建框架,用实现来扩展细节。
2)软件的需求发生变化的时候,尽量通过扩展软件实体的行为来实现变化,而不是修改已有的代码。
3)编程中使用其他的原则、设计模式的目的就是为了软件设计能够遵循开闭原则(可见开闭原则之重要!)
如上述依赖倒置原则的代码就是符合开闭原则的设计
6. 迪米特法则
定义:
1)一个对象应该对其他的对象保持最少的了解
2)类与类之间的关系越密切,其耦合度越高
3)迪米特法则又叫最少直到原则,即一个类对自己所依赖的类知道的越少越好。也就是说,对于被依赖的类不管有多复杂,都尽量将逻辑封装到类的内部。对外只提供一个public方法即可,而不需要对外泄露任何信息
4)迪米特法则还有一个更加简单的定义:只与直接的朋友进行通信
5)直接的朋友:一个类A与类B之间具有耦合关系,耦合关系有很多种:依赖、关联、组合、聚合等等。那么当B出现在A的成员变量、方法参数或方法返回值中的时候,称B是A的直接朋友。而当B出现在A的局部变量中的时候,B不是A的直接朋友。也即 4)的另一种说法就是,陌生的类最好不要以局部变量的形式出现在类的内部。
如果在开发过程中,类B并不是类A的直接朋友,那么需要按照迪米特法则,要避免在类A中出现这样非直接朋友关系的耦合。
a. 那么具体的使用步骤是什么呢?
-
检查类A中有哪些直接朋友(在成员变量、方法参数、方法返回值中出现的类)和陌生类(以局部变量的形式出现的类)
-
分析使用到了陌生类B的方法F,判断是不是可以将该功能移植到陌生类其他依赖的类中去实现,并在记为F1,并通过将方法参数传入方法F中,并在方法F中调用相关方法(F1)以实现该功能。如下所示:
class A { // B类出现在了方法F的内部,这时候需要根据迪米特法则对F进行修改。 public void F(BManager manager) { List<B> list = manager.getBs(); for (B b : list) { print(b); } } } class BManager { public List<B> getBs(){ return ...; } } class B { }
修改后的代码为:
class A { // 这样修改,A中将不会在方法内部只用陌生类B了。 public void F(BManager manager) { manager.F1(); } } class BManager { public List<B> getBs(){ return ...; } public void F1() { List<B> list = this.getBs(); for (B b : list) { print(b); } } } class B { }
b. 迪米特法则使用的注意事项
(1)迪米特法则的核心是降低类之间的耦合度
(2)迪米特法则只要求降低类(对象)之间的耦合关系,而不是要消除耦合关系。
7. 合成复用原则
基本介绍:尽量使用合成/聚合的方式,而不是继承的方式。
设计原则的核心思想就是:
- 找出应用中可能需要变化的地方,把他们独立出来,不要与那些不需要变化的代码混在一起
- 面向接口编程而不是面向具体实现编程
- 不断地对交互对象进行松耦合设计
二、UML类图设计
1. 实体
主要包括了类和接口
2. 关系
1)依赖(Dependency)
如下图所示,在PersonServiceBean类中使用到了IDCard类、Person类、PersonDao类、Department类;那么可以说PersonServiceBean类依赖于其他四个类。
关于依赖有几种写法:(1)A类是B类的成员变量、(2)A类是B类中方法的返回值类型、(3)A类是B类中方法的接收参数类型、(4)A类是在B类的方法内部被使用到了,以局部变量的形式。以上四种情况都可以说明,B类依赖于A类。
该关系的UML表达为:(注意:依赖关系是一个虚线+箭头的形式)
2)泛化(继承)(Generalization)是依赖关系的一种特例
泛化关系就是继承关系,即如果PersonServiceBean类继承了DaoSupport类,就说PersonServiceBean和DaoSupport之间存在泛化关系。其关系的UML表达为:
3)实现(Realization) 也是一个依赖关系的特例
实现也是依赖关系的一个特例,即PersonServiceBean 类实现了PersonService接口。其UML表达如下(注意是虚线):
4)关联(Association)
关联关系实际上就是类与类之间的关系,具有导航性(即双向关系或者单向关系)、多重性(即一对一、一对多、多对多)等
5)聚合(Aggregation)
聚合关系表达的是整体与部分的关系,整体和部分是可以分开的。聚合关系是关联关系的特例,所以它同样具有导航性和多重性。
Computer类中有Mouse类和Moniter类的成员变量,如果电脑与显示器和鼠标是可以分离的,那么这就是聚合关系。其代码的实现为:
class Computer {
// 如果是可以分离,将Mouse 和 Moniter类定义之后不去new一个对象的话,就是可分离的聚合关系【与下边的组合进行对比】
private Mouse mouse;
private Moniter moniter;
public void setMouse(Mouse mouse){...}
public void setMoniter(Moniter moniter){...}
}
class Mouse {
}
class Moniter {
}
聚合关系的UML表示为:(实线+空心菱形)
6)组合(Composite) 级联删除
同上边 5)中的聚合关系,当整体与部分不可分离的时候,聚合关系升级为组合关系。其代码实现为:
// Computer 类
class Computer {
// 将Mouse 和 Moniter类定义后new一个对象的话,就是不可分离的状态,
// 此时为组合关系【Mouse和Moniter类随着Computer类的创建而创建,销毁而销毁】
private Mouse mouse = new Mouse();
private Moniter moniter = new Moniter();
}
// Mouse 类
class Mouse {
}
// Moniter 类
class Moniter {
}
组合关系的UML表示为:
组合与聚合的一个案例
class Person {
// 因为人(Person)与头(Head)是不可分离的,那么这两者是组合的关系,而人(Person)和身份证(IDCard)是可以分离的。
private Head head = new Head();
private IDCard idCard;
}
class Head {
}
class IDCard {
}
三、设计模式的分类与概述
设计模式分为三种类型、共23种
-
创建型模式:(共5种)
单例模式(Singleton)— 8种实现方式
原型模式(Prototype)— 对象的clone,深拷贝、浅拷贝问题
建造者模式(Builder)
工厂方法模式(Factory Method)
抽象工厂模式(Abstract Factory Method)
-
结构型模式:(共7种)
适配器模式(Adaptor)
桥接模式(Bridge)
装饰模式(Decorator)— 解决类爆炸问题
组合模式(Composite)
外观模式、享元模式(Flyweight)
代理模式(Proxy)
-
行为型模式:(共11种)(在方法的角度)
模板方法模式(Template Method)
命令模式(Command)
访问者模式(Visitor)
迭代器模式(Iterator)
观察者模式(Observer)
中介者模式(Mediator)
备忘录模式(Memento)
解释器模式(Interpreter)
状态模式(State)
策略模式(Strategy)
责任链模式 (Chain of Responsibility)