前言
设计模式原则
开闭原则 | 对扩展开放,对修改关闭 |
单一职责原则 | 类职责应尽可能单一 |
里氏替代原则 | 只要父类能出现的地方,子类就可以出现 |
依赖倒置原则 | 细节应该依赖抽象 |
接口隔离原则 | 每个接口中不存在子类用不到却必须实现的方法 |
迪米特法则 / 最少知识原则 | 一个对象对其依赖应尽可能少的了解 |
合成复用原则 | 优先使用组合 / 聚合,其次考虑继承 |
概述
行为型模式用于
- 描述程序在运行时复杂的流程控制
- 描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务
- 分配算法与对象间的职责
模板方法模式 | 定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤 |
策略模式 | 该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。 |
状态模式 | 对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。 |
命令模式 | 将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理 |
责任链模式 | 为了避免请求发送者与多个请求处理者耦合在一起,将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止 |
观察者模式 | 指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式 |
中介者模式 | 定义一个中介对象来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互。中介者模式又叫调停模式,它是迪米特法则的典型应用 |
备忘录模式 | 在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态 |
解释器模式 | 给分析对象定义一个语言,并定义该语言的文法表示,再设计一个解析器来解释语言中的句子 |
迭代器模式 | 提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示 |
访问者模式 | 将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式 |
理解
十一种行为型模式中,除了模板方法模式和解释器模式是类行为型模式,其他的全部属于对象行为型模式
迭代器模式和访问者模式似乎有点晦涩,主要应用于数据结构中
模板方法模式
定义
定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤
UML图
主要角色
-
抽象类:负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成
-
具体子类:实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的一个组成步骤
-
模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法
-
基本方法——抽象方法:在抽象类中申明,由具体子类实现
-
基本方法——具体方法:在抽象类中已经实现,子类可直接继承且一般不需要重写
-
基本方法——钩子方法:在抽象类中通常空实现(或非侵入式实现),子类可选择性重写
钩子方法的作用
在算法中习惯性称之为钩子函数,一般认为是回调函数的一种。通常用于前置拦截或监听
回调函数、钩子函数以函数指针实现
钩子函数在捕获消息的第一时间触发,回调函数在捕获结束后触发
接口空实现(不推荐)
假设需要实现一个接口的某个方法
可以实现它的全部方法
但是如果只需要其中的一个方法
可以以一个抽象类实现这个接口,然后用钩子方法空实现,实现类只需继承这个抽象类即可
举个栗子
似乎Java8
的默认接口方法更适合于模板方法模式
抽象计算器
public abstract class AbstractCalculator {
// 模板方法
public final int getResult(String expression) {
hook(expression);
return calculate(expression);
}
// 基本方法——抽象方法
public abstract int calculate(String expression);
// 基本方法——具体方法
public int add(int a, int b) { return a + b; }
public int subtract(int a, int b) { return a - b; }
public int multiply(int a, int b) { return a * b; }
public int divided(int a, int b) { return a / b; }
// 基本方法——钩子方法
public void hook(String expression) { }
}
具体计算器
public class SimpleCalculator extends AbstractCalculator {
@Override
public int calculate(String expression) {
int a = Integer.parseInt(String.valueOf(expression.charAt(0)));
int b = Integer.parseInt(String.valueOf(expression.charAt(2)));
switch (expression.charAt(1)) {
case '+': return super.add(a, b);
case '-': return super.subtract(a, b);
case '*': return super.multiply(a, b);
case '/': return super.divided(a, b);
default : return 0;
}
}
@Override
public void hook(String expression) {
if (expression.charAt(2) == 48) {
System.out.println("除0错误");
System.exit(0);
}
}
}
Client
public class Client {
public static void main(String[] args) {
AbstractCalculator calculator = new SimpleCalculator();
int result = calculator.getResult("2/0");
System.out.println(result);
}
}
策略模式
定义
该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户
UML图
主要角色
-
抽象策略:定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,环境角色使用这个接口调用不同的算法,一般使用接口或抽象类实现
-
具体策略:实现了抽象策略定义的接口,提供具体的算法实现
-
环境:持有一个策略类的引用,最终给客户端调用
概括
算法的实现不影响算法的功能。(冒泡排序和快速排序不影响排序的功能)
状态模式
1. 定义
对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为
UML图
主要角色
-
环境:也称为上下文,它定义了客户感兴趣的接口,维护一个当前状态,并将与状态相关的操作委托给当前状态对象来处理
-
抽象状态:定义一个接口,用以封装环境对象中的特定状态所对应的行为
-
具体状态:实现抽象状态所对应的行为
举个栗子
电梯的状态维护表
状态1 / 状态2 | Closed | Opened | Running | Stopped |
---|---|---|---|---|
Closed | / | + | + | + |
Opened | + | / | - | / |
Running | / | - | / | + |
Stopped | + | + | + | / |
状态的维护中,无权限的状态转换以及不合理的状态转换均可视为不响应
环境——电梯
// 电梯
public class Elevator {
public static final AbstractState OPENED = new Opened();
public static final AbstractState CLOSED = new Closed();
public static final AbstractState RUNNING = new Running();
public static final AbstractState STOPPED = new Stopped();
@Getter
@Setter
private AbstractState state;
public void open() { this.state.open(this); }
public void close() { this.state.close(this); }
public void run() { this.state.run(this); }
public void stop() { this.state.stop(this); }
}
抽象状态
// 抽象状态
public abstract class AbstractState {
public abstract void open (Elevator context);
public abstract void close(Elevator context);
public abstract void run (Elevator context);
public abstract void stop (Elevator context);
}
关闭状态
// 关门状态
public class Closed extends AbstractState {
@Override
public void open(Elevator context) {
// operation
System.out.println("开门");
// 状态维护转移
context.setState(Elevator.OPENED);
}
// 不合理的状态转换
@Override
public void close(Elevator context) {
// do nothing
System.out.println("已经关闭");
}
@Override
public void run(Elevator context) {
// operation
System.out.println("运行");
// 状态维护转移
context.setState(Elevator.RUNNING);
}
@Override
public void stop(Elevator context) {
// operation
System.out.println("关门");
// 状态维护转移
context.setState(Elevator.STOPPED);
}
}
开启状态
// 开启状态
public class Opened extends AbstractState {
// 不合理的状态转换
@Override
public void open(Elevator context) {
System.out.println("已经开启");
}
@Override
public void close(Elevator context) {
System.out.println("关门");
context.setState(Elevator.CLOSED);
}
// 无权限的状态转换
@Override
public void run(Elevator context) {
// no nothing
}
// 无权限的状态转换
@Override
public void stop(Elevator context) {
// do nothing
}
}
运行状态
// 运行状态
public class Running extends AbstractState {
// 无权限的状态
@Override
public void open(Elevator context) {
// do nothing
}
// 不合理的状态转换
@Override
public void close(Elevator context) {
// do nothing
}
//不合理的状态转换
@Override
public void run(Elevator context) {
System.out.println("正在运行");
}
@Override
public void stop(Elevator context) {
System.out.println("停止");
context.setState(Elevator.STOPPED);
}
}
停止状态
public class Stopped extends AbstractState {
@Override
public void open(Elevator context) {
System.out.println("开门");
context.setState(Elevator.OPENED);
}
@Override
public void close(Elevator context) {
System.out.println("停止");
context.setState(Elevator.CLOSED);
}
@Override
public void run(Elevator context) {
System.out.println("运行");
context.setState(Elevator.RUNNING);
}
// 不合理的状态转换
@Override
public void stop(Elevator context) {
System.out.println("已经停止");
}
}
Client
public class Client {
public static void main(String[] args) {
Elevator elevator = new Elevator();
// default
elevator.setState(Elevator.STOPPED);
// 状态切换会自行维护
elevator.open();
elevator.close();
elevator.run();
elevator.stop();
}
}
一句话概括状态模式
解开业务算法与状态维护算法的耦合,让状态自我维护
命令模式
定义
将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理
UML图
主要角色
-
抽象命令:声明执行命令的接口,拥有执行命令的抽象方法 execute()
-
具体命令:是抽象命令类的具体实现类,它拥有接收者对象,并通过调用接收者的功能来完成命令要执行的操作
-
请求者 / 调用者:是请求的发送者,它通常拥有很多的命令对象,并通过访问命令对象来执行相关请求,它不直接访问接收者
-
接收者 / 被调用者:执行命令功能的相关操作,是具体命令对象业务的真正实现者
责任链模式
定义
为了避免请求发送者与多个请求处理者耦合在一起,将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止
UML图
主要角色
-
抽象处理者:定义一个处理请求的接口,包含抽象处理方法和一个后继连接
-
具体处理者:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者
举个栗子
有一个请假服务
由学生发起,由女朋友、班长、班主任、教导主任审批
学生不关心审批过程,只关心审批结果
审批者不关心处理流程,只关心自己能不能处理,不能处理应该交给谁处理
假设学生有一个请假服务
public class StudentServiceImpl implements StudentService {
@Override
public boolean leave(LeaveInfo leaveInfo, Handler handler) {
// 请假服务不关心处理过程,只关心请假服务最终的执行结果
return handler.handle(leaveInfo);
}
}
拟定一个假条的请求载体
@Getter
public class LeaveInfo {
private final String studentName;
private final Integer day;
private final String message;
public LeaveInfo(String studentName, Integer day, String message) {
this.studentName = studentName;
this.day = day;
this.message = message;
}
}
抽象处理者
public abstract class Handler {
protected Handler nextHandler;
// 链式setter
public Handler setNextHandler(Handler nextHandler) {
this.nextHandler = nextHandler;
return this;
}
/**
* @param leaveInfo 假条信息
* @return 处理结果
*/
public abstract boolean handle(LeaveInfo leaveInfo);
}
具体处理者(女朋友、班长、班主任、教导主任)
handle()
的具体实现本应该只是相似而非雷同,代码只为了起到演示所有才几进相同的。(比如说班花请假时,班长应该直接拒绝,为什么?因为班花不允许请假!)
@Slf4j
public class Girlfriend extends Handler {
@Override
public boolean handle(LeaveInfo leaveInfo) {
if (leaveInfo.getDay() <= 1) {
// 有权处理,通过
log.info(this.getClass().getSimpleName() + " 通过 ");
return true;
} else if (nextHandler == null) {
// 有权处理,拒绝
log.info(this.getClass().getSimpleName() + " 拒绝 ");
return false;
} else {
// 无权处理,交给下一个处理器
log.info(this.getClass().getSimpleName() + "交个上级");
return nextHandler.handle(leaveInfo);
}
}
}
@Slf4j
public class Monitor extends Handler {
@Override
public boolean handle(LeaveInfo leaveInfo) {
if (leaveInfo.getDay() <= 3) {
log.info(this.getClass().getSimpleName() + " 通过 ");
return true;
} else if (nextHandler == null) {
log.info(this.getClass().getSimpleName() + " 拒绝 ");
return false;
} else {
log.info(this.getClass().getSimpleName() + "交个上级");
return nextHandler.handle(leaveInfo);
}
}
}
@Slf4j
public class ClassTeacher extends Handler {
@Override
public boolean handle(LeaveInfo leaveInfo) {
if (leaveInfo.getDay() <= 7) {
log.info(this.getClass().getSimpleName() + " 通过 ");
return true;
} else if (nextHandler == null) {
log.info(this.getClass().getSimpleName() + " 拒绝 ");
return false;
} else {
log.info(this.getClass().getSimpleName() + "交个上级");
return nextHandler.handle(leaveInfo);
}
}
}
@Slf4j
public class Dean extends Handler {
@Override
public boolean handle(LeaveInfo leaveInfo) {
if (leaveInfo.getDay() <= 31) {
log.info(this.getClass().getSimpleName() + " 通过 ");
return true;
} else if (nextHandler == null) {
log.info(this.getClass().getSimpleName() + " 拒绝 ");
return false;
} else {
log.info(this.getClass().getSimpleName() + "交个上级");
return nextHandler.handle(leaveInfo);
}
}
}
Client模拟请假
public class Application {
public static void main(String[] args) {
// 学生服务
// 其中请假服务需要假条参数,责任链参数
StudentService studentService = new StudentServiceImpl();
// 一条责任链
Handler dean = new Dean();
Handler classTeach = new ClassTeacher().setNextHandler(dean);
Handler monitor = new Monitor().setNextHandler(classTeach);
Handler girlfriend = new Girlfriend().setNextHandler(monitor);
// 执行请假
// param1: 假条 param2: 责任链底端
boolean result = studentService.leave(new LeaveInfo("教主", 6, "世界那么大,我想去看看"), girlfriend);
System.out.println("最后审批结果" + result);
}
}
一句话概括责任链模式模式
遍历行为链表(数据元素为处理请求行为的链表)
典型应用
过滤器、拦截器、异常链、SpringAOP
通知
观察者模式
定义
指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式
UML图
主要角色
-
抽象主题:提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法
-
具体主题:它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象
-
抽象观察者:包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用
-
具体观察者:实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态
观察者模式概括与个人误区
单纯的观察者模式不是指放哨
称观察者模式为发布——订阅模式视乎更好一些,是一个通知——自更新的过程。比如一个网站发布某新内容,订阅该网站的公众号、邮件、门户墙等等自更新
中介者模式
定义
定义一个中介对象来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互。中介者模式又叫调停模式,它是迪米特法则的典型应用
UML图
主要角色
-
抽象中介者:它是中介者的接口,提供了同事对象注册与转发同事对象信息的抽象方法
-
具体中介者:实现中介者接口,定义一个 List 来管理同事对象,协调各个同事角色之间的交互关系,因此它依赖于同事角色
-
抽象同事:定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能
-
具体同事:是抽象同事类的实现者,当需要与其他同事对象交互时,由中介者对象负责后续的交互
直观的对比中介者模式的作用
举个栗子
人力资源市场与黑中介
备忘录模式
定义
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态
UML图
主要角色
-
发起人:记录当前时刻的内部状态信息, 提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息
-
备忘录:负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人
-
管理者:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改
概括与误区
备忘录不是指table
,而是指row
,其中state
是一个可能需要备份的字段
备忘录模式相当于用备忘录脚手架生成一条记录存到管理者中
举个栗子
发起人
@Data
@AllArgsConstructor
public class User {
private String name;
private Integer age;
private String predecessor;
// 创建备忘录
public Memento<String> createMemento() {
Memento<String> memento = new Memento<>();
memento.setState(this.predecessor);
return memento;
}
// 恢复备忘录
public void restoreMemento(Memento<String> memento) {
this.predecessor = memento.getState();
}
// operation
public void deletePredecessorPhone() {
this.predecessor = null;
}
}
备忘录
// 备忘录
public class Memento<T> {
// state字段
@Getter
@Setter
private T state;
}
管理者(么得感情)
public class Phone {
// 暂存最近的一条备忘录记录
// 如果需要备份多次记录,可能需要用Map结构
@Getter
@Setter
private Memento<?> memento;
}
Client
public class Client {
@SuppressWarnings("unchecked")
public static void main(String[] args) {
User local = new User("XXX", 19, "YYY");
Phone phone = new Phone();
// 状态1
System.out.println(local);
// 存档后更新状态
phone.setMemento(local.createMemento());
// 发生了点情况
local.deletePredecessorPhone();
System.out.println(local);
// 恢复状态
local.restoreMemento((Memento<String>) phone.getMemento());
System.out.println(local);
}
}
解释器模式
定义
给分析对象定义一个语言,并定义该语言的文法表示,再设计一个解析器来解释语言中的句子。也就是说,用编译语言的方式来分析应用中的实例。这种模式实现了文法表达式处理的接口,该接口解释一个特定的上下文
UML图
主要角色
-
抽象表达式:定义解释器的接口,约定解释器的解释操作,主要包含解释方法 interpret()
-
终结符表达式:用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式与之相对应
-
非终结符表达式:用来实现文法中与非终结符相关的操作,文法中的每条规则都对应于一个非终结符表达式
-
环境:通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值
解释器模式的理解
解释器模式最核心的可能不是对终端表达式和非终端表达式的建模。可能将某个语言解析成一个表达式树的算法更为核心
举个栗子
环境——计算器
parse()将中缀表达式字符串解析为表达式树
operation()从树根递归计算
终端表达式——变量
非终端表达式——操作符
迭代器模式
定义
提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示
UML图
主要角色
-
抽象聚合:定义存储、添加、删除聚合对象以及创建迭代器对象的接口
-
具体聚合:实现抽象聚合类,返回一个具体迭代器的实例
-
抽象迭代器:定义访问和遍历聚合元素的接口,通常包含 hasNext()、first()、next() 等方法
-
具体迭代器:实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置
应用场景
-
当需要为聚合对象提供多种遍历方式时
-
当需要为遍历不同的聚合结构提供一个统一的接口时
-
当访问一个聚合对象的内容而无需暴露其内部细节的表示时
访问者模式
定义
将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。它将对数据的操作与数据结构进行分离
UML图
主要角色
-
抽象访问者:定义一个访问具体元素的接口,为每个具体元素类对应一个访问操作 visit() ,该操作中的参数类型标识了被访问的具体元素
-
具体访问者:实现抽象访问者角色中声明的各个访问操作,确定访问者访问一个元素时该做什么
-
具体元素:实现抽象元素角色提供的 accept() 操作,其方法体通常都是 visitor.visit(this) ,另外具体元素中可能还包含本身业务逻辑的相关操作
-
对象结构:是一个包含元素角色的容器,提供让访问者对象遍历容器中的所有元素的方法,通常由 List、Set、Map 等聚合类实现