# 行为型模式

行为型模式用于描述程序在运行时复杂的流程控制,即表述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对对象间职责的分配

行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足 “合成复用原则”, 所以对象行为模式比类行为模式具有更大的灵活性

行为型模式分为 :

  • 模板方法模式
  • 策略模式
  • 命令模式
  • 职责链模式
  • 状态模式
  • 观察者模式
  • 中介者模式
  • 迭代器模式
  • 访问者模式
  • 备忘录模式
  • 解释器模式

# 模板方法模式

定义一个操作中的算法股价,而将算法的一些步骤延迟到子类中,使得子类可以在不改变算法结构的情况下重定义该算法的某些特定步骤

模板方法 (Template Method) 模式包含以下主要角色 :

  • 抽象类 (Abstract Class) : 负责给出一个算法的轮廓和骨架,它由一个模板方法和若干个基本方法构成

    • 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法

    • 基本方法:是实现算法各个步骤的方法,是模板方法的组成部分。基本方法又可以分为三种

      • 抽象方法 (Abstract Method) : 一个抽象方法由抽象类声明,由具体子类实现

      • 具体方法 (Concrete Method) : 一个具体方法由一个抽象类或具体类声明并实现,其子类可以进行覆盖也可以直接继承

      • 钩子方法 (Hook Method) : 在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种

        一般钩子方法是用于判断的逻辑方法,这类方法名一般为 isXXX, 返回值类型为 boolean 类型

    • 具体子类 (Concrete Class) : 实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的组成步骤

例:炒菜的步骤是固定的,分为倒油,热油,倒蔬菜,倒调料品,翻炒等步骤.

image-20221123195034431

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
public abstract class AbstractClass {
public final void cookProcess() {
pourOil();
heatOil();
pourVegetable();
pourSauce();
fry();
}

public void pourOil() {
System.out.println("倒油");
}

public void heatOil() {
System.out.println("热油");
}

public abstract void pourVegetable();

public abstract void pourSauce();

public void fry() {
System.out.println("炒蔬菜");
}
}


public class BaiCai extends AbstractClass{
@Override
public void pourVegetable() {
System.out.println("下锅的蔬菜是白菜");
}

@Override
public void pourSauce() {
System.out.println("倒盐");
}
}



public class CaiXin extends AbstractClass{
@Override
public void pourVegetable() {
System.out.println("下锅的蔬菜是菜心");
}

@Override
public void pourSauce() {
System.out.println("倒糖");
}
}


public class Client {
public static void main(String[] args) {
CaiXin caiXin = new CaiXin();
caiXin.cookProcess();
BaiCai baiCai = new BaiCai();
baiCai.cookProcess();
}
}

优点 :

  • 提高代码复用性

    将相同部分的代码放在抽象的父类中,而将不同的代码放入不同的子类中

  • 实现了反向控制

    通过一个父类调用其子类的操作,通过对子类的具体实现扩展不同的行为,实现了反向控制,并符合开闭原则

缺点 :

  • 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象
  • 父类的抽象方法由子类实现,子类执行结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读难度

# 适用场景

  • 算法的整体步骤很固定,但其中个别部分易变时,这时候可以适用模板方法模式,将容易变的部分抽象出来,供子类实现
  • 需要通过子类来决定父类算法中某个步骤是否执行,实现子类对父类的反向控制

# 策略模式

该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响适用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理

策略模式的主要角色如下 :

  • 抽象策略 (Strategy) 类:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口
  • 具体策略 (Concrete Strategy) 类:实现了抽象策略定义的接口,提供具体的算法实现或行为
  • 环境 (Context) 类:持有一个策略类的引用,最终给客户端调用

例:百货公司指定年度促销活动,针对不同的节日推出不同的促销活动,由促销员将促销活动展示给用户

image-20221123203618400

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class SaleMan {
private Strategy strategy;

public SaleMan(Strategy strategy) {
this.strategy = strategy;
}

public void salesManShow() {
System.out.println("促销员正在展示 : ");
strategy.show();
}
}


public interface Strategy {
void show();
}



public class StrategyA implements Strategy {
@Override
public void show() {
System.out.println("买一送一");
}
}



public class StrategyB implements Strategy {
@Override
public void show() {
System.out.println("全场八折");
}
}


public class StrategyC implements Strategy {
@Override
public void show() {
System.out.println("满一百减一百");
}
}

优点 :

  • 策略类之间可以自由切换

    由于策略类都是先同一个接口,所以使它们之间可以自由切换

  • 易于扩展

    增加一个新的策略只需要添加一个具体的策略类即可,基本不需要改变原有的代码,符合开闭原则

  • 避免使用多重条件选择语句 (if else), 充分体现面对对象设计思想

缺点 :

  • 客户端必须知道所有的策略类,并自行决定适用哪一个策略类
  • 策略模式将产生很多策略类,可以通过适用享元模式在一定程度上减少对象的数量

# 适用场景

  • 一个系统需要动态地在几种算法中选择一种时,可将每个算法封装到策略类
  • 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,可将每个条件分支移入它们各自的策略类中以代替这些条件语句
  • 系统中各算法彼此完全独立,且要求对客户端隐藏具体算法的实现细节时
  • 系统要求使用算法的客户不应该知道其操作的数据时,可适用策略模式来隐藏与算法相关的数据结构
  • 多个类只区别在表现行为不同,可以适用策略模式,在运行时动态选择具体要执行的行为

# 命令模式

将一个请求封装为对象,使发出请求的责任和执行请求的责任分隔开,这样两者之间通过命令对象进行沟通,这样方便将命令对象进行存储,传递,调用,增加与管理

命令模式包含以下主要角色 :

  • 抽象命令类 (Command) 角色:定义命令的接口,声明执行的方法
  • 具体命令 (Concrete Command) 角色:具体的命令,实现命令接口;通常会持有接收者,并调用接收者的功能来完成命令要执行的操作
  • 实现者 / 接收者 (Receiver) 角色:接收者,真正执行命令的对象,任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能
  • 调用者 / 请求者 (Invoker) 角色:要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口

例:顾客把订单交给招待,招待将订单给厨师,初始根据订单准备餐点

image-20221123210627268

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
public class Client {
public static void main(String[] args) {
Order order1 = new Order(1);
order1.addFoods("西红柿炒蛋", 1);
order1.addFoods("小杯可乐", 1);

Order order2 = new Order(2);
order2.addFoods("西红柿炒蛋面条", 1);
order2.addFoods("大杯可乐", 1);

Cook cook = new Cook();

OrderCommand cmd1 = new OrderCommand(order1, cook);
OrderCommand cmd2 = new OrderCommand(order2, cook);

Waiter waiter = new Waiter();
waiter.addOrderCommand(cmd1);
waiter.addOrderCommand(cmd2);
waiter.orderUp();
}
}


public class Waiter {
private List<OrderCommand> commands = new ArrayList<>();
public void addOrderCommand(OrderCommand orderCommand) {
commands.add(orderCommand);
}

public void orderUp() {
for (OrderCommand command : commands) {
command.execute();
}
}
}


public interface Command {
void execute();
}


public class Cook {
public void makeFood(String name, int num) {
System.out.println("厨师做好了" + name + ", 共" + num + "份");
}
}


public class Order {
private int tableNumber;

public int getTableNumber() {
return tableNumber;
}

public Map<String, Integer> getFoods() {
return foods;
}

private Map<String, Integer> foods = new HashMap<>();

public void addFoods(String name, int num) {
foods.put(name, num);
}

public Order(int tableNumber) {
this.tableNumber = tableNumber;
}
}



public class OrderCommand implements Command {
private Order order;
private Cook cook;

public OrderCommand(Order order, Cook cook) {
this.order = order;
this.cook = cook;
}

@Override
public void execute() {
order.getFoods().forEach(cook::makeFood);
}
}

优点 :

  • 降低系统的耦合度。命令模式能将调用操作的对象与实现该操作的对象解耦
  • 增加或删除命令非常方便,采用命令模式增加与删除命令不会影响其他类,它满足开闭原则,对扩展比较灵活
  • 可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令
  • 方便实现 Undo 和 Redo 操作。命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复

缺点 :

  • 使用命令模式可能会导致某些系统有过多的具体命令类
  • 系统结构更加复杂

# 适用场景

  • 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互
  • 系统需要在不同的事件指定请求,将请求排队和执行请求
  • 系统需要支持命令的撤销操作和恢复操作

# 责任链模式

又名职责链模式,为了避免请求发送这与多个请求处理者耦合在一起,将所有请求的处理者通过迁移对象记住下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,知道有对象处理它为止

责任链模式主要包含以下角色 :

  • 抽象处理者 (Handler) 角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接
  • 具体处理者 (Concrete Handler) 角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将请求转给它的后继者
  • 客户类 (Client) 角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程

例:请假流程控制系统,请假一天以内小组长同意即可;一天到三天需要部门经理统一;三天到七天总经理统一

image-20221123220025018

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
public abstract class Handler {
protected final static int NUM_ONE = 1;
protected final static int NUM_THREE = 3;
protected final static int NUM_SEVEN = 7;

private int numStart;
private int numEnd;

private Handler nextHandler;

public Handler(int numStart, int numEnd) {
this.numStart = numStart;
this.numEnd = numEnd;
}

public Handler(int numStart) {
this.numStart = numStart;
}

public void setNextHandler(Handler nextHandler) {
this.nextHandler = nextHandler;
}

public abstract void handleLeave(LeaveRequest request);

public final void submit(LeaveRequest request) {
this.handleLeave(request);
if (this.nextHandler != null && request.getDuration() > this.numEnd)
this.nextHandler.submit(request);
else
System.out.println("流程结束");
}
}


public class LeaveRequest {
private String name;
private int duration;
private String content;

public LeaveRequest(String name, int duration, String content) {
this.name = name;
this.duration = duration;
this.content = content;
}

public String getName() {
return name;
}

public int getDuration() {
return duration;
}

public String getContent() {
return content;
}
}



public class GroupLeader extends Handler{
public GroupLeader() {
super(0, Handler.NUM_ONE);
}

@Override
public void handleLeave(LeaveRequest request) {
System.out.printf("%s请假%d天%n", request.getName(), request.getDuration());
System.out.println("小组长审批: 同意");
}
}



public class Manager extends Handler{
public Manager() {
super(Handler.NUM_ONE, Handler.NUM_ONE);
}

@Override
public void handleLeave(LeaveRequest request) {
System.out.printf("%s请假%d天%n", request.getName(), request.getDuration());
System.out.println("部门经理: 同意");
}
}


public class GeneralManager extends Handler{
public GeneralManager() {
super(Handler.NUM_ONE, Handler.NUM_ONE);
}

@Override
public void handleLeave(LeaveRequest request) {
System.out.printf("%s请假%d天%n", request.getName(), request.getDuration());
System.out.println("总经理: 同意");
}
}



public class Client {
public static void main(String[] args) {
LeaveRequest request = new LeaveRequest("赵子升", 4, "操你妈");
GroupLeader groupLeader = new GroupLeader();
Manager manager = new Manager();
GeneralManager generalManager = new GeneralManager();
groupLeader.setNextHandler(manager);
manager.setNextHandler(generalManager);
groupLeader.submit(request);
}
}

优点 :

  • 降低了对象之间的耦合度

    该模式降低了请求发送者和接收者的耦合度

  • 增强了系统的可扩展性

    可以根据需要增加新的请求处理类,满足开闭原则

  • 增强了给对象指派职责的灵活性

    当工作流程发生变化,可以动态地改变链内的成员或修改它们的次序,也可动态地新增或删除责任

  • 责任链简化了对象之间的连接

    一个对象只需保持一个指向其后继者的引用,不需要保持其他所有处理者的引用,这避免了众多的 if 或者 if else 语句

  • 责任分担

    每个类只需要处理自己该处理的工作,不能处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则

缺点 :

  • 不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理
  • 对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响
  • 职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用

# 状态模式

对有状态的对象,把复杂的 “判断逻辑” 提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为

状态模式包含以下角色 :

  • 环境 (Context) 角色:也称上下文,它定义了客户程序需要的接口,维护一个当前状态,并将与状态相关的操作委托给当前状态对象来处理
  • 抽象状态 (State) 角色:定义一个接口,用以封装环境对象中的特尔顶状态所对应的行为
  • 具体状态 (Concrete State) 角色:实现抽象状态所对应的行为

例:投票系统,一人一票,超过一票显示重复刷票,超过 5 票定义为恶意刷票并删除数据,超过 8 票则禁止登录

image-20221125173847038

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
public interface VoteState {
void vote(String user, String voteItem, VoteManager voteManager);
}



public class NormalVoteState implements VoteState{
@Override
public void vote(String user, String voteItem, VoteManager voteManager) {
voteManager.getVoteMap().put(user, voteItem);
System.out.println("投票成功");
}
}


public class RepeatVoteState implements VoteState{
@Override
public void vote(String user, String voteItem, VoteManager voteManager) {
System.out.println("请勿重复刷票");
}
}


public class SpiteVoteState implements VoteState{
@Override
public void vote(String user, String voteItem, VoteManager voteManager) {
voteManager.getVoteMap().remove(user);
System.out.println("禁止恶意刷票, 已删除投票数据");
}
}


public class BlackVoteState implements VoteState{
@Override
public void vote(String user, String voteItem, VoteManager voteManager) {
System.out.println("禁止登录系统");
}
}



public class VoteManager {
private VoteState voteState;
private Map<String, String> voteMap = new HashMap<>();
private Map<String, Integer> voteCountMap = new HashMap<>();

public void vote(String user, String voteItem) {
Integer oldCount = voteCountMap.get(user);
if (oldCount == null || oldCount < 1) {
this.voteState = new NormalVoteState();
} else if (oldCount < 5)
this.voteState = new RepeatVoteState();
else if (oldCount < 8)
this.voteState = new SpiteVoteState();
else
this.voteState = new BlackVoteState();

voteCountMap.put(user, oldCount == null ? 1 : oldCount + 1);
this.voteState.vote(user, voteItem, this);
}

public Map<String, String> getVoteMap() {
return voteMap;
}

public void setVoteMap(Map<String, String> voteMap) {
this.voteMap = voteMap;
}
}



public class Client {
public static void main(String[] args) {
VoteManager manager = new VoteManager();
for (int i = 0; i < 9; i++) {
manager.vote("赵子升", "王帅");
}
}
}

优点 :

  • 将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为
  • 允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块

缺点 :

  • 状态模式的适用必然会增加系统类和对象的个数
  • 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱
  • 状态模式对 “开闭原则” 的支持并不太好

# 适用场景

  • 当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为,就可以考虑适用状态模式
  • 一个操作中含有庞大的分支结构,并且这些分支决定于对象的状态时

# 观察者模式

又称为发布 - 订阅 (Publish/Subscribe) 模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有的观察者对象,使它们能够自动更新自己

在观察者模式中有如下角色 :

  • Subject : 抽象主题 (抽象被观察者), 抽象主题角色把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供要给接口,可以增加和删除观察者对象
  • ConcreteSubject : 具体主题 (具体被观察者), 该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知
  • Observer : 抽象观察者,是观察者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己
  • ConrreteObserver : 具体观察者,实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态

例:公众号

image-20221125175118079

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
public interface Subject {
void addObserver(Observer observer);
void removeObserver(Observer observer);
void notifyOneObserver(int index);
void notifyAllObserver();
}


public class SubscribeAccount implements Subject{
private List<Observer> observerList = new ArrayList<>();
@Override
public void addObserver(Observer observer) {
observerList.add(observer);
}

@Override
public void removeObserver(Observer observer) {
observerList.remove(observer);
}

@Override
public void notifyOneObserver(int index) {
observerList.get(index).doAction();
}

@Override
public void notifyAllObserver() {
observerList.forEach(Observer::doAction);
}
}



public interface Observer {
void doAction();
}


public class SubscriberA implements Observer{
@Override
public void doAction() {
System.out.println("订阅者A觉得这个公众号很不错");
}
}


public class SubscriberB implements Observer{
@Override
public void doAction() {
System.out.println("订阅者B觉得这个公众号很不错");
}
}


public class SubscriberC implements Observer{
@Override
public void doAction() {
System.out.println("订阅者C觉得这个公众号很不错");
}
}


public class Client {
public static void main(String[] args) {
SubscribeAccount account = new SubscribeAccount();
account.addObserver(new SubscriberA());
account.addObserver(new SubscriberB());
account.addObserver(new SubscriberC());
account.notifyOneObserver(1);
System.out.println("===================");
account.notifyAllObserver();
}
}

优点 :

  • 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系
  • 被观察者发送通知,所有注册的观察者都会收到信息 (可以实现广播机制)

缺点 :

  • 如果观察者非常多的话,那么所有的观察者收到被观察者发送的通知会耗时
  • 如果被观察者有循环依赖的话,那么被观察者发送通知会使观察者循环调用,会导致系统崩溃

# 适用场景

  • 对象存在一对多关系,一个对象的状态发生改变会影响其他对象
  • 当一个抽象模型有两个方面,其中一个方面依赖于另一方面时

# 中介模式

又称调停模式,定义一个中介角色来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变他们之间的交互

终结者模式包含以下主要角色 :

  • 抽象中介者 (Mediator) 角色:他是中介者的接口,提供了同时对象注册与转发同时对象信息的抽象方法
  • 具体中介者 (ConcreteMediator) 角色:实现中介者接口,定义一个 List 来管理同事对象,协调各个同时角色之间的交互关系
  • 抽象同事 (Colleague) 角色:定义同事类的接口,保存终结者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能
  • 具体同事类 (Concrete Colleague) 角色:是抽象同事类的实现者,当需要与其他同时对象交互时,由中介者对象负责后续的交互

例:租房通过房屋中介,房主将房屋托管给房屋中介,而租房者从房屋中介获取房屋信息。房屋中介充当租房者与房屋所有者之间的中介者

image-20221125200238032

优点 :

  • 松散耦合

    中介者模式通过把多个同时对象之间的交互封装到中介者对象里面,从而使得同事对象之间的松散耦合,基本上可以做到互不依赖。同事对象就可以独立地变化和复用

  • 集中控制交互

    多个同时对象的交互,被封装在中介者对象里面集中管理,使得这些交互行为发生变化的时候,只需要修改中介者对象就可以了,当然如果是已经做好的系统,那么就扩展中介者对象,而各个同事类不需要做修改

  • 一对多关联转变为一对一关联

    没有使用中介者模式的时候,同事对象之间的关系通常是一对多的,引入中介者对象以后,中介者对象和同事对象的关系通常变为双向的一对一

缺点 :

  • 当同事类太多时,中介者的职责将很大,他会变得复杂而庞大,以至于系统难以维护

# 适用场景

  • 系统中对象之间存在复杂的引用关系,系统结构混乱难以理解
  • 当想创建一个运行于多个类之间的 对象,又不想生成新的子类时

# 迭代器模式

提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示

迭代器模式主要包含以下角色 :

  • 抽象聚合 (Aggregate) 角色:定义存储,添加,删除聚合元素以及创建迭代器对象的接口
  • 具体聚合 (ConcreteAggregate) 角色:实现抽象聚合类,返回一个具体迭代器的实例
  • 抽象迭代器 (Iterator) 角色:定义访问和遍历聚合元素的接口,通常包含 hasNext (), next () 等方法
  • 具体迭代器 (ConcreteIterator) 角色:实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置

例:学生列表的迭代器

image-20221125204203220

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
public class Student {
private String name;
private String number;

public Student(String name, String number) {
this.name = name;
this.number = number;
}

@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", number='" + number + '\'' +
'}';
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}


public interface StudentAggregate {
void addStudent(Student student);

void removeStudent(Student student);

StudentIterator getStudentIterator();
}


public class StudentAggregateImpl implements StudentAggregate{
private List<Student> list = new ArrayList<>();
@Override
public void addStudent(Student student) {
list.add(student);
}

@Override
public void removeStudent(Student student) {
list.remove(student);
}

@Override
public StudentIterator getStudentIterator() {
return new StudentIteratorImpl(list);
}
}



public interface StudentIterator {
boolean hasNext();
Student next();
}



public class StudentIteratorImpl implements StudentIterator{
private List<Student> list;
private int position = 0;

public StudentIteratorImpl(List<Student> list) {
this.list = list;
}

@Override
public boolean hasNext() {
return position < list.size();
}

@Override
public Student next() {
return list.get(position++);
}
}


public class Client {
public static void main(String[] args) {
StudentAggregate aggregate = new StudentAggregateImpl();
aggregate.addStudent(new Student("张三", "0001"));
aggregate.addStudent(new Student("李四", "0002"));
aggregate.addStudent(new Student("王五", "0003"));
aggregate.addStudent(new Student("赵六", "0004"));
aggregate.addStudent(new Student("孙七", "0005"));

StudentIterator studentIterator = aggregate.getStudentIterator();
while(studentIterator.hasNext())
System.out.println(studentIterator.next());
}
}

优点 :

  • 它支持以不同的方式遍历一个聚合对象,在同一个聚合对象上可以定义多种遍历方式,在迭代器模式中只需要用一个不同的迭代器替代原有迭代器即可改变遍历算法,也可以自定义迭代器的子类以支持新的遍历方式
  • 迭代器简化了聚合类。在原有的聚合对象中不需要再自行提供数据遍历等方法,这样可以简化聚合类的设计
  • 在迭代器模式中,由于引入了抽象层,增加新的聚合类和迭代器类都很方便,无须修改原有代码,满足开闭原则要求

缺点 :

  • 增加了类的个数,这在一定程度上增加了系统的复杂性

# 适用场景

  • 当需要为聚合对象提供多种遍历方式时
  • 当需要为遍历不同的聚合结构提供一个统一的接口时
  • 当访问一个聚合对象的内容无需暴露其内部细节的表示时

# 访问者模式

封装一些作用于某种数据结构中的各元素的操作,它可以在不改变这个数据结构的前提下定义作用于这些元素的新的操作

访问者模式包含以下主要角色 :

  • 抽象访问者 (Visitor) 角色:定义了对每一个元素访问的行为,它的参数就是可以访问的元素,他的方法个数理论上来讲与元素类个数是一样的
  • 具体访问者 (ConcreteVisitor) 角色:给出对每一个元素类访问时所产生的具体行为
  • 抽象元素 (Element) 角色:定义了一个接受访问者的方法,其意义是指,每一个元素都要可以被访问者访问
  • 具体元素 (ConcreteElement) 角色:提供接受访问方法的具体实现,而这个具体的实现,通常情况下是适用访问者提供的访问该元素类的方法
  • 对象结构 (Object Structure) 角色:定义当中所遇到的对象结构,对象结构是一个抽象表述,可以理解为一个具有容器性质或复合对象特性的类,它会含有一组元素,并且可以迭代这些元素,供访问者访问

例:给宠物喂食

  • 访问者角色:给宠物喂食的人
  • 具体访问者角色:主人,其他人
  • 抽象元素角色:动物抽象类
  • 具体元素角色:宠物狗,宠物猫
  • 结构对象角色:主人家

优点 :

  • 扩展性好

    在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能

  • 复用性好

    通过访问者来定义整个对象结构通用的功能,从而提高复用程度

  • 分离无关行为

    通过访问者来分离无关的行为,把相关的行为封装在一起,构成一个访问者,这样每一个访问者的功能都比较单一

缺点 :

  • 对象结构变化很困难

    在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了开闭原则

  • 违反了依赖倒置原则

    访问者模式依赖了具体类,而没有依赖抽象类

# 适用场景

  • 对象结构相对稳定,但其操作算法经常变化的程序
  • 对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构

# 备忘录模式

又称快照模式,在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态

备忘录模式的主要角色如下 :

  • 发起人 (Originator) 角色:记录当前时刻的内部状态信息,提供创建备忘录和回复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息
  • 备忘录 (Memento) 角色:负责储存发起人的内部状态,在需要的时候提供这些内部状态给发起人
  • 管理者 (Caretaker) 角色:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内部进行访问与修改

备忘录有两个等效的接口 :

  • 窄接口:管理者对象看到的是备忘录的窄接口,这个接口只允许他把备忘录对象传给其他的对象
  • 宽接口:与管理者看到的窄接口相反,发起人对象可以看到一个款接口,这个宽接口允许它读取所有的数据,以便根据这些数据恢复这个发起人对象的内部状态

例:游戏角色打怪失败恢复

# 白箱备忘录模式

备忘录角色对任何对象都提供一个接口,即宽接口,备忘录角色的内部所存储的状态就对所有对象公开

image-20221125214709161

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
public class GameRole {
private int vit;
private int atk;
private int def;

public void recoverState(RoleStateMemento memento) {
this.vit = memento.getVit();
this.atk = memento.getAtk();
this.def = memento.getDef();
}

public void stateDisplay() {
System.out.printf("角色生命力: %d\n角色攻击力: %d\n角色防御力: %d\n", this.vit, this.atk, this.def);
}

public void initState() {
this.vit = 100;
this.atk = 100;
this.def = 100;
}

public void fight() {
this.vit = 0;
this.atk = 0;
this.def = 0;
}

public RoleStateMemento saveState() {
return new RoleStateMemento(this.vit, this.atk, this.def);
}
}


public class RoleStateMemento {
private int vit;
private int atk;
private int def;


public int getVit() {
return vit;
}

public int getAtk() {
return atk;
}

public int getDef() {
return def;
}

public RoleStateMemento(int vit, int atk, int def) {
this.vit = vit;
this.atk = atk;
this.def = def;
}
}


public class RoleStateCaretaker {
private RoleStateMemento roleStateMemento;

public RoleStateMemento getRoleStateMemento() {
return roleStateMemento;
}

public void setRoleStateMemento(RoleStateMemento roleStateMemento) {
this.roleStateMemento = roleStateMemento;
}
}


public class Client {
public static void main(String[] args) {
System.out.println("打boss前===========================");
GameRole role = new GameRole();
role.initState();
role.stateDisplay();
RoleStateCaretaker caretaker = new RoleStateCaretaker();
caretaker.setRoleStateMemento(role.saveState());
System.out.println("打boss后===========================");
role.fight();
role.stateDisplay();
System.out.println("恢复之后============================");
role.recoverState(caretaker.getRoleStateMemento());
role.stateDisplay();
}
}

白箱备忘录模式是破坏封装性的,但是通过程序员自律,同样可以在一定程度上实现模式的大部分用意

# 黑箱备忘录模式

备忘录角色对发起人对象提供一个宽接口,而为其他对象提供一个窄接口。在 Java 中,实现双重接口的办法就是将备忘录设计成发起人类的内部成员类

image-20221125220616945

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
public class GameRole {
private int vit;
private int atk;
private int def;

public void recoverState(Memento memento) {
RoleStateMemento roleStateMemento = (RoleStateMemento) memento;
this.vit = roleStateMemento.getVit();
this.atk = roleStateMemento.getAtk();
this.def = roleStateMemento.getDef();
}

public void stateDisplay() {
System.out.printf("角色生命力: %d\n角色攻击力: %d\n角色防御力: %d\n", this.vit, this.atk, this.def);
}

public void initState() {
this.vit = 100;
this.atk = 100;
this.def = 100;
}

public void fight() {
this.vit = 0;
this.atk = 0;
this.def = 0;
}

public Memento saveState() {
return new RoleStateMemento(this.vit, this.atk, this.def);
}

private class RoleStateMemento implements Memento {
private int vit;
private int atk;
private int def;

public int getVit() {
return vit;
}

public int getAtk() {
return atk;
}

public int getDef() {
return def;
}

public RoleStateMemento(int vit, int atk, int def) {
this.vit = vit;
this.atk = atk;
this.def = def;
}
}
}


public interface Memento {
}


public class RoleStateCaretaker {
private Memento memento;

public Memento getMemento() {
return memento;
}

public void setMemento(Memento memento) {
this.memento = memento;
}
}



public class Client {
public static void main(String[] args) {
System.out.println("打boss前===========================");
GameRole role = new GameRole();
role.initState();
role.stateDisplay();
RoleStateCaretaker caretaker = new RoleStateCaretaker();
caretaker.setMemento(role.saveState());
System.out.println("打boss后===========================");
role.fight();
role.stateDisplay();
System.out.println("恢复之后============================");
role.recoverState(caretaker.getMemento());
role.stateDisplay();
}
}

优点 :

  • 提供了一种可以恢复状态的机制。当用户需要时能够比较方便地将数据恢复到某个历史状态
  • 实现了内部状态的封装。除了创建它的发起人之外,其他对象都不能访问这些状态信息
  • 简化了发起人类。发起人不需要管理和和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理,这符合单一职责原则

缺点 :

  • 资源消耗大。如果要保存的内部状态信息过多或者特别繁琐,将会占用比较大的内存资源

# 适用场景

  • 需要保存于恢复数据的场景,如玩游戏时的中间结果的存档功能
  • 需要提供一个可回滚操作的场景

# 解释器模式

给定一个语言,定义它的文法表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子

解释器模式包含以下主要角色 :

  • 抽象表达式 (Abstract Expression) 角色:定义解释器的接口,约定解释器的解释操作,主要包含解释方法 interpret ()
  • 终结符表达式 (Terminal Expression) 角色:是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式与之相对应
  • 非终结符表达式 (Nonterminal Expression) 角色:也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应一个非终结符表达式
  • 环境 (Context) 角色:通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值
  • 客户端 (Client) : 主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法,当然也可以通过环境角色间接访问解释器的解释方法

例:设计实现加减法软件

image-20221125225528354

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
public abstract class AbstractExpression {
public abstract int interpret(Context context);
}


public class Context {
private Map<Variable, Integer> map = new HashMap<>();

public void assign(Variable var, Integer value) {
map.put(var, value);
}

public int getValue(Variable var) {
return map.get(var);
}
}



public class Minus extends AbstractExpression{
private AbstractExpression left;
private AbstractExpression right;

public Minus(AbstractExpression left, AbstractExpression right) {
this.left = left;
this.right = right;
}

@Override
public int interpret(Context context) {
return left.interpret(context) - right.interpret(context);
}

@Override
public String toString() {
return "Plus{" +
"left=" + left +
", right=" + right +
'}';
}
}



public class Plus extends AbstractExpression{
private AbstractExpression left;
private AbstractExpression right;

public Plus(AbstractExpression left, AbstractExpression right) {
this.left = left;
this.right = right;
}

@Override
public int interpret(Context context) {
return left.interpret(context) + right.interpret(context);
}

@Override
public String toString() {
return "Plus{" +
"left=" + left +
", right=" + right +
'}';
}
}



public class Variable extends AbstractExpression{
private String name;

public Variable(String name) {
this.name = name;
}

@Override
public String toString() {
return "Variable{" +
"name='" + name + '\'' +
'}';
}

@Override
public int interpret(Context context) {
return context.getValue(this);
}
}

优点 :

  • 易于改变和扩展文法

    由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。每一条文法规则都可以表示为一个类,因此可以方便地实现一个简单的语言

  • 实现文法较为容易

    在抽象语法树中每一个表达式节点类的实现方式都是相似的,这些类的代码编写都不会特别复杂

  • 增加新的解释表达式较为方便

    如果用户需要增加新的解释表达式只需要对应增加一个新的终结符表达式或非终结符表达式类,原有表达式类代码无需修改,符合开闭原则

# 适用场景

  • 当语言的文法较为简单,且执行效率不是关键问题时
  • 当问题重复出现,且可以用一种简单的语言来表达时
  • 当一个语言需要解释执行,并且语言中的句子可以表示为一个抽象语法树的时候