行为型模式

一、概述

行为型模式(Behavioral Pattern)关注系统中对象之间的交互,研究系统在运行时对象之间的相互通信与协作,进一步明确对象的职责

行为型模式不仅仅关注类和对象本身,还重点关注它们之间的相互作用和职责划分

行为型模式可以分为类行为型模式对象行为型模式两种

类行为型模式通过继承关系在几个类之间分配行为,主要通过多态等方式来分配父类与子类的职责

对象行为型模式通过对象的关联关系来分配行为,主要通过对象关联来分配两个或多个类的职责

根据合成复用原则,大部分行为型模式都属于对象行为型设计模式

行为型模式共有十一种:

设计原则名称 定义 使用频率
职责链模式
(Chain of Responsibility Pattern)
避免将一个请求的发送者与接收者耦合在一起,让多个对象都有机会处理请求。将接受请求的对象连成一条链,并且沿着这条链传递请求,直到有一个对象能够处理它为止 ⭐⭐
命令模式
(Command Pattern)
将一个请求封装为一个对象,从而可用不同的请求对客户进行参数化,对请求排队或者记录请求日志,以及支持可撤销的操作 ⭐⭐⭐⭐
解释器模式
(Interpreter Pattern)
给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子
迭代器模式
(Interator Pattern)
提供一种方法顺序访问一个聚合对象的各个元素,而又不用暴露该对象内部表示 ⭐⭐⭐⭐⭐
中介者模式
(Mediator Pattern)
定义一个对象来封装一系列对象的交互。中介者模式是各对象之间不需要显示地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互 ⭐⭐
备忘录模式
(Memento Pattern)
在不破坏封装的前提下捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态 ⭐⭐
观察者模式
(Oberver Pattern)
定义对象之间的一种一对多依赖关系,使得每当一个对象状态发生改变时其相关依赖对象皆得到通知并被自动更新 ⭐⭐⭐⭐⭐
状态模式
(State Pattern)
允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类 ⭐⭐⭐
策略模式
(Strategy Pattern)
定义一系列算法,将每一个算法封装起来,并让它们可以相互替换,策略模式让算法可以独立于使用它的客户而变化 ⭐⭐⭐⭐
模板方法模式
(Template Method Pattern)
定义一个操作中算法的框架,而将一些步骤延迟到子类中。模板方法模式使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤 ⭐⭐⭐
访问者模式
(Visitor Pattern)
表示一个作用于某对象结构中的各个元素的操作。访问者模式让用户可以在不改变各元素的类的前提下定义作用于这些元素的新操作

二、职责链模式

某公司欲开发一个软件系统的在线文档帮助系统,用户可以在任何一个查询环境中输入查询关键字,如果当前查询环境下没有相关内容,则系统会将查询按照一定的顺序转发给其他查询环境。
设查询环境如下:JavaSearchContext、SQLSearchContext、UMLSearchContext

1. 抽象处理者

1.1 AbstractSearchContext

1
2
3
4
5
6
7
8
9
10
public abstract class AbstractSearchContext {

protected AbstractSearchContext abstractSearchContext;

public void setAbstractSearchContext(AbstractSearchContext abstractSearchContext) {
this.abstractSearchContext = abstractSearchContext;
}

public abstract void handleRequest(String request);
}

2. 具体处理者

2.1 JavaSearchContext

1
2
3
4
5
6
7
8
9
10
public class JavaSearchContext extends AbstractSearchContext {
@Override
public void handleRequest(String request) {
if (request.equals("Java")) {
System.out.println("在Java中搜索到该关键字");
} else {
this.abstractSearchContext.handleRequest(request);
}
}
}

2.2 SQLSearchContext

1
2
3
4
5
6
7
8
9
10
public class SQLSearchContext extends AbstractSearchContext {
@Override
public void handleRequest(String request) {
if (request.equals("SQL")) {
System.out.println("在SQL中搜索到该关键字");
} else {
this.abstractSearchContext.handleRequest(request);
}
}
}

2.3 UMLSearchContext

1
2
3
4
5
6
7
8
9
10
public class UMLSearchContext extends AbstractSearchContext {
@Override
public void handleRequest(String request) {
if (request.equals("UML")) {
System.out.println("在UML中搜索到该关键字");
} else {
System.out.println("该关键字在Java、SQL和UML中均不存在");
}
}
}

3. 客户端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void main(String[] args) {
AbstractSearchContext javaSC, sqlSC, umlSC;
javaSC = new JavaSearchContext();
sqlSC = new SQLSearchContext();
umlSC = new UMLSearchContext();

javaSC.setAbstractSearchContext(sqlSC);
sqlSC.setAbstractSearchContext(umlSC);

System.out.println("-----------测试一-----------");
javaSC.handleRequest("Java");
System.out.println("-----------测试二-----------");
javaSC.handleRequest("SQL");
System.out.println("-----------测试三-----------");
javaSC.handleRequest("UML");
System.out.println("-----------测试四-----------");
javaSC.handleRequest("Python");
}

4. 优缺点和适用场景

4.1 优点

  • 使得一个对象无须知道是其他哪个对象处理请求,对象仅需知道该请求会被处理
  • 链中的对象不知道链子的结构,由客户端进行创建,降低了系统的耦合
  • 链中的对象仅需要维持一个后继者的引用,而不是链子中的所有成员,简化了对象之间的相互连接
  • 支持动态的增删链中的对象,灵活性较强,并且无须修改其他类,符合开闭原则

4.2 缺点

  • 职责链较长时,牵连对象增多,增加系统的复杂度,并且不太好进行代码调试

4.3 适用场景

  • 有多个对象可以处理同一个请求,具体那个对象处理该请求待运行时刻确认,客户端只管将请求提交到链上
  • 在不指明指定的接收者时,向链子提交请求
  • 客户端可以控制链子的具体对象和先后顺序以及长短
  • 涉及对象的数量适中

三、命令模式

别名动作(Action)模式事务(Transaction)模式

在PC端操作时,鼠标右键(RightClick)功能在桌面时可以用于显示桌面右键菜单(由DisplayDesktopRightClickMenu实现),在LOL游戏中还可以用于移动角色(由LolMoveCharacter实现)
现使用命令模式设计该系统,使得功能键类与功能类之间解耦,可以为同一个功能键设置不同的功能

1. 抽象命令类

1.1 Command

1
2
3
4
5
public abstract class Command {

public abstract void execute();

}

2. 具体命令类

2.1 DisplayCommand

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class DisplayCommand extends Command {

private DisplayDesktopRightClickMenu desktopRightClickMenu;

public DisplayCommand() {
desktopRightClickMenu = new DisplayDesktopRightClickMenu();
}

@Override
public void execute() {
desktopRightClickMenu.display();
}

}

2.2 MoveCommand

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MoveCommand extends Command {

private LolMoveCharacter lolMoveCharacter;

public MoveCommand() {
lolMoveCharacter = new LolMoveCharacter();
}

@Override
public void execute() {
lolMoveCharacter.move();
}

}

3. 调用者(Invoker)

3.1 RightClick

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class RightClick {

private Command command;

public void setCommand(Command command) {
this.command = command;
}

public void click() {
System.out.println("单击右键");
command.execute();
}

}

4. 接收者(Receiver)

4.1 DisplayDesktopRightClickMenu

1
2
3
4
5
6
7
public class DisplayDesktopRightClickMenu {

public void display() {
System.out.println("显示桌面右键菜单");
}

}

4.2 LolMoveCharacter

1
2
3
4
5
6
7
public class LolMoveCharacter {

public void move() {
System.out.println("LOL游戏移动人物");
}

}

5. 客户端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Client {
public static void main(String[] args) {
RightClick rightClick = new RightClick();
Command moveCommand, displayCommand;
moveCommand = new MoveCommand(); // 可以通过配置文件注入
displayCommand = new DisplayCommand();

rightClick.setCommand(moveCommand);
rightClick.click();

rightClick.setCommand(displayCommand);
rightClick.click();
}
}

6. 优缺点及适用场景

6.1 优点

  • 降低系统的耦合度。调用者与接收者之间不存在直接的引用
  • 符合开闭原则。新的命令可以很容易地加入到系统中,并且不会对系统源代码进行修改
  • 可以容易地设计一个命令队列或宏命令(组合命令,也是组合模式和命令模式的集成)
  • 为请求的撤销(Undo)和恢复(Redo)操作提供了一种设计和实现方案

6.2 缺点

  • 增加系统复杂度和理解成本。每一个请求接收者都会对应一个具体命令类

6.3 适用场景

  • 调用者和请求接收者解耦
  • 需要请求排队执行
  • 支持请求的撤销(Undo)和恢复(Redo)操作
  • 需要将一组操作组合在一起形成宏命令

四、解释器模式

目前主要应用于正则表达式、XML稳定解释、Eclipse AST等
由于该模式使用频率较低,并且系统结构较为复杂,本文不以案例展示,仅介绍其优缺点及适用场景

1. 优缺点及适用场景

1.1 优点

  • 易于改变和扩展文法
  • 每一条文法规则都可以表示为一个类
  • 实现文法较为容易
  • 增加新的解释表达式较为方便

1.2 缺点

  • 对于复杂文法难以维护
  • 执行效率较低

1.3 适用场景

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

五、迭代器模式

某商品管理系统的商品名称存储在一个字符串数组中,现需要自定义一个双向迭代器(MyIterator)实现对该商品名称数组的双向(前向和后向)遍历

一、抽象聚合类

1.1 AbstractArray

1
2
3
4
5
6
7
8
9
public abstract class AbstractArray {
public AbstractArray(Object[] objects) {
this.objects = objects;
}

protected Object[] objects;

protected abstract AbstractIterator createIterator();
}

二、抽象迭代器

2.1 AbstractIterator

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface AbstractIterator {
void next();

boolean isLast();

void previous();

boolean isFirst();

Object getNextItem();

Object getPreviousItem();
}

三、具体聚合类和具体迭代器(内部类实现)

3.1 GoodsArray

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
public class GoodsArray extends AbstractArray {

public GoodsArray(Object[] objects) {
super(objects);
}

// 具体迭代器
private class Itr implements AbstractIterator {

int cursor1;
int cursor2;

public Itr() {
this.cursor1 = 0;
this.cursor2 = objects.length - 1;
}

@Override
public void next() {
if (cursor1 < objects.length - 1) {
cursor1++;
}
}

@Override
public boolean isLast() {
return (cursor1 == objects.length - 1);
}

@Override
public void previous() {
if (cursor2 > 0) {
cursor2--;
}
}

@Override
public boolean isFirst() {
return (cursor2 == 0);
}

@Override
public Object getNextItem() {
return objects[cursor1];
}

@Override
public Object getPreviousItem() {
return objects[cursor2];
}
}

@Override
protected AbstractIterator createIterator() {
return new Itr();
}
}

四、客户端代码

1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args) {
String[] goodsName = new String[100];
for (int i = 0; i < goodsName.length - 1; i++) {
goodsName[i] = String.valueOf(i);
}
AbstractArray goodsArray = new GoodsArray(goodsName);
AbstractIterator goodsArrayItr = goodsArray.createIterator();
while (!goodsArrayItr.isLast()){
goodsArrayItr.next();
System.out.println(goodsArrayItr.getNextItem());
}
}

五、优缺点及适用场景

5.1 优点

  • 支持不同的方式遍历一个聚合对象
  • 简化了聚合类

5.2 缺点

  • 增加了系统复杂度。因为将存储数据和遍历数据的职责分离,需要新增迭代器类,类的个数成对增加
  • 设计难度较大。需要考虑一个全面的迭代器不是一件容易的事情

5.3 适用场景

  • 访问一个聚合对象的内容而无须暴露它的内部表示
  • 需要为一个聚合对象提供多种遍历模式
  • 为遍历不同的聚合对象提供一个统一的接口

六、中介者模式

虚拟聊天室 在“虚拟聊天室”实例中增加一个新的具体聊天室类和一个新的具体会员类,要求如下:
(1) 新的具体聊天室中发送的图片大小不得超过20
(2) 新的具体聊天室中发送的文字信息的长度不得超过100个字符,提供更强大的不雅字符过滤功能(如可过滤TMD、“操”等字符)。
(3) 新的具体会员类可以发送图片信息和文字信息。
(4) 新的具体会员类在发送文本信息时,可以在信息后加上发送时间,格式为:文本信息(发送时间)

1. 抽象中介者

1.1 AbstractChatRoom

1
2
3
4
5
6
7
8
9
public abstract class AbstractChatRoom {

protected abstract void sendInfo(AbstractMember m, String info);

protected abstract void sendPic(AbstractMember m, String pic);

protected abstract void sendInfoAndPic(AbstractMember m, String info, String pic);

}

2. 具体中介者

2.1 ChatRoom

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
public class ChatRoom extends AbstractChatRoom {

private final ArrayList<AbstractMember> members = new ArrayList<>();

@Override
protected void sendInfo(AbstractMember m, String info) {
if (m.getClass() == Member.class) {
m.sendInfo(info);
members.forEach(AbstractMember::receive);
} else if (m.getClass() == Vip.class) {
m.sendInfo(info);
members.forEach(AbstractMember::receive);
}
}

@Override
protected void sendPic(AbstractMember m, String pic) {
if (m.getClass() == Member.class) {
m.sendPic(pic);
members.forEach(AbstractMember::receive);
} else if (m.getClass() == Vip.class) {
m.sendPic(pic);
members.forEach(AbstractMember::receive);
}
}

@Override
protected void sendInfoAndPic(AbstractMember m, String info, String pic) {
if (m.getClass() == Member.class) {
m.sendInfoAndPic(info, pic);
members.forEach(AbstractMember::receive);
} else if (m.getClass() == Vip.class) {
m.sendInfoAndPic(info, pic);
members.forEach(AbstractMember::receive);
}
}

public ArrayList<AbstractMember> getMembers() {
return members;
}

}

3. 抽象同事类

3.1 AbstractMember

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
public abstract class AbstractMember {

private AbstractChatRoom chatRoom;

public abstract void receive();

public void setChatRoom(AbstractChatRoom chatRoom) {
this.chatRoom = chatRoom;
}

public void cSendInfo(String info) {
chatRoom.sendInfo(this, info);
}

public void cSendPic(String pic) {
chatRoom.sendPic(this, pic);
}

public void cSendInfoAndPic(String info, String pic) {
chatRoom.sendInfoAndPic(this, info, pic);
}

public void sendInfo(String info) {
chatRoom.sendInfo(this, info);
}

public void sendPic(String pic) {
chatRoom.sendPic(this, pic);
}

public void sendInfoAndPic(String info, String pic) {
chatRoom.sendInfoAndPic(this, info, pic);
}

}

4. 具体同事类

4.1 Member

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
// 部分代码简略实现
public class Member extends AbstractMember {

private String name;

@Override
public void receive() {
System.out.println(this.getName() + "接收消息");
}

@Override
public void sendInfo(String info) {
if (info.length() > 100) {
System.out.println("消息过长!");
return;
} else {
String f1 = info.replaceAll("TMD", "*");
info = f1.replaceAll("操", "*");
}
System.out.println(this.getName() + "发送了消息:" + info);
}

@Override
public void sendPic(String pic) {
if (pic.length() > 100) {
System.out.println("照片过大!");
return;
}
System.out.println(this.getName() + "发送了照片:" + pic);
}

@Override
public void sendInfoAndPic(String info, String photo) {
System.out.println("普通成员不能同时发送信息和照片");
}


public String getName() {
return name;
}

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

}

4.2 Vip

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
// 部分代码简略实现
public class Vip extends AbstractMember {

private String name;

@Override
public void receive() {
System.out.println(this.getName() + "接收消息");
}

@Override
public void sendInfo(String info) {
if (info.length() > 100) {
System.out.println("消息过长!");
return;
} else {
String f1 = info.replaceAll("TMD", "*");
info = f1.replaceAll("操", "*");
}
System.out.println(this.getName() + "发送了消息:" + info + "(" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) + ")");
}

@Override
public void sendPic(String pic) {
if (pic.length() > 100) {
System.out.println("照片过大!");
return;
}
System.out.println(this.getName() + "发送了照片:" + pic + "(" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss:SSS")) + ")");
}

@Override
public void sendInfoAndPic(String info, String pic) {
if (info.length() > 100) {
System.out.println("消息过长!");
return;
} else {
String f1 = info.replaceAll("TMD", "*");
info = f1.replaceAll("操", "*");
}
if (pic.length() > 100) {
System.out.println("照片过大!");
return;
}
System.out.println(this.getName() + "发送了消息和照片:" + info + pic + "(" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss:SSS")) + ")");
}


public String getName() {
return name;
}

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

}

5. 客户端代码

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
public class Client {
public static void main(String[] args) {
ChatRoom chatRoom = new ChatRoom();

Member member1 = new Member();
Member member2 = new Member();
Vip vip1 = new Vip();
Vip vip2 = new Vip();

member1.setName("member1");
member2.setName("member2");
vip1.setName("vip1");
vip2.setName("vip2");

member1.setChatRoom(chatRoom);
member2.setChatRoom(chatRoom);
vip1.setChatRoom(chatRoom);
vip2.setChatRoom(chatRoom);

chatRoom.getMembers().add(member1);
chatRoom.getMembers().add(member2);
chatRoom.getMembers().add(vip1);
chatRoom.getMembers().add(vip2);

member1.cSendInfo("付老师讲得课真好!");
member1.cSendPic("/member1表情包.jpg");
member2.cSendPic("/member2表情包.jpg");
vip1.cSendInfoAndPic("操!member1你和我想的一样","/vip1表情包.jpg");
vip2.cSendInfoAndPic("哈哈哈","/vip2表情包.jpg");
}
}

6. 优缺点及适用场景

6.1 优点

  • 简化对象之间的交互,适用中介者和同事的一对多关系,代替了原来同事间的多对多关系
  • 将具体同事类之间解耦。例如上述例子中的具体同事类发送和接收消息的过程,如果是中介者和同事的一对多关系,那么中间件循环调用具体同事类的操作即可;如果是同事间的多对多关系,那么就需要在具体同时类中增加相应方法,这样会导致耦合度急剧增加
  • 可以减少子类的生成。如果需要改变中介者的内容,只需要另外建立具体中介者(继承抽象中介者),而不需要对同事类进行扩展,这使得各个同事类之间可以被复用

6.2 缺点

  • 提高系统维护成本。具体中介者中会包含大量同事类之间的交互细节

6.3 适用场景

  • 系统中对象之间存在复杂的引用关系(多对多)
  • 一个对象由于引用了其他很多对象并且直接和这些对象通信,导致难以复用该对象
  • 想通过一个中间类来封装多个类中的行为,而又不想生成太多的子类,此时可以通过引入中介者类来实现,在中介者类中定义对象交互的公共行为

七、备忘录模式

目前主要应用于文字处理软件、图像编辑软件、数据库管理系统中
由于该模式使用频率较低,并且会导致系统小豪过大,本文不以案例展示,仅介绍其优缺点及适用场景

1. 优缺点及适用场景

1.1 优点

  • 提供了一种状态恢复机制,方便用户回到特定的历史步骤

1.2 缺点

  • 资源消耗过大。每保存一次对象的状态都需要消耗一定的系统资源

1.3 适用场景

  • 需要将一个对象在某一时刻的全部状态或部分状态记录下来,以方便将来回到先前的状态

八、观察者模式

某在线股票软件需要提供如下功能:当股票购买者所购买的某支股票价格变化幅度达到5%时,系统将自动发送通知(包括新价格)给购买股票的股民。
现使用观察者模式设计该系统

1. 抽象目标类

1.1 AbstractStock

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
public abstract class AbstractStock {

protected String name;

protected double amount;

protected ArrayList<AbstractStockBuyer> stockBuyers = new ArrayList<>();

public String getName() {
return name;
}

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

public void addStockBuyer(AbstractStockBuyer stockBuyerObserver) {
System.out.println(stockBuyerObserver.getName() + "购买了" + this.name + "股票!");
stockBuyers.add(stockBuyerObserver);
}

public abstract void priceChange(double change);

public abstract void notifyPriceChange(double change, double amount);
}

2. 具体目标类

2.1 Stock

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Stock extends AbstractStock {

public Stock(String name, double amount) {
System.out.println(name + "股票以" + amount + "元上市啦!");
System.out.println("-----------------------");
this.name = name;
this.amount = amount;
}


@Override
public void priceChange(double change) {
this.amount = amount * (1.00 + change * 0.01);
if (change >= 5.00) {
notifyPriceChange(change, amount);
}
}

@Override
public void notifyPriceChange(double change, double amount) {
stockBuyers.forEach(as -> as.observePriceChange(change, amount));
}
}

3. 抽象观察者

3.1 AbstractStockBuyer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public abstract class AbstractStockBuyer {

private String name;

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

public String getName() {
return this.name;
}

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

public void buy(AbstractStock as) {
as.addStockBuyer(this);
}

public abstract void observePriceChange(double change, double amount);
}

4. 具体观察者

4.1 StockBuyer

1
2
3
4
5
6
7
8
9
10
11
12
public class StockBuyer extends AbstractStockBuyer {

public StockBuyer(String name) {
super(name);
}

@Override
public void observePriceChange(double change, double amount) {
System.out.println("提醒:" + this.getName() + ",股票加个变化幅度达到了" + change + "%,现在已经达到了" + String.format("%.2f", amount) + "元,快快来买!");
}

}

5. 客户端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Client {
public static void main(String[] args) {
AbstractStock stock = new Stock("xxxx",100000);

AbstractStockBuyer buyer1 = new StockBuyer("buyer1");
AbstractStockBuyer buyer2 = new StockBuyer("buyer2");
AbstractStockBuyer buyer3 = new StockBuyer("buyer3");
AbstractStockBuyer buyer4 = new StockBuyer("buyer4");

buyer1.buy(stock);
buyer2.buy(stock);
buyer3.buy(stock);
buyer4.buy(stock);

stock.priceChange(3);
stock.priceChange(10);
}
}

6. 优缺点及适用场景

6.1 优点

  • 在目标和观察者之间建立一个抽象的耦合。目标只需要维持这一个抽象观察者的集合,而无须了解其具体观察者
  • 支持广播通信
  • 符合开闭原则。增加新的具体观察者也无须修改原有代码

6.2 缺点

  • 如果目标有很多直接和间接观察者,将所有观察者都通知到位会花费很多时间
  • 如果观察者和观察目标之间存在循环依赖,可能会导致系统崩溃
  • 没有相应的机制让观察者知道目标对象是如何发生变化的,而仅仅只是知道其变化了

6.3 适用场景

  • 一个抽象模型有两个方面,其中一个方面依赖另一个方面,将这两个方面封装在独立的对象中使他们可以各自独立地改变和复用
  • 需要在系统种创建一个触发链
  • 一个对象的改变可能会引起一个或者多个其他对象也发生改变

九、状态模式

某公司要为一银行开发一套信用卡业务系统,银行账户(Account)是该系统的核心类之一,通过分析,账户存在 3 中状态且在不同状态下账户存在不同的行为,具体说明如下:

  • 账户余额(balance)≥ 0,正常状态(NormalState),此时既可以存款也可以取款
  • 2000 < 账户余额(balance)< 0,透支状态(OverdraftState),此时既可以存款也可以取款,但需要按天计算利息
  • 账户余额(balance)≤ 2000,受限状态(RestrictedState),此时只能向账户存款而不能取款
  • 根据余额的不同,以上 3 种状态可以相互转换

试使用状态模式设计

1. 环境类

1.1 Account

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
public class Account {

private AccountState state;

private String owner;

private double balance = 0;

public Account(String owner, double balance) {
this.owner = owner;
this.balance = balance;
state = new NormalState(this);
}

public void setState(AccountState state) {
this.state = state;
}

public double getBalance() {
return balance;
}

public void setBalance(double balance) {
this.balance = balance;
}

public void deposit(double amount) {
System.out.println(this.owner + "存款" + amount);
state.deposit(amount);
System.out.println("现在余额为:" + this.balance);
System.out.println("现在账户状态为:" + this.state.getClass().getName());
System.out.println("-----------------------------------------------");
}

public void withdraw(double amount) {
System.out.println(this.owner + "取款" + amount);
state.withdraw(amount);
System.out.println("现在余额为:" + this.balance);
System.out.println("现在账户状态为:" + this.state.getClass().getName());
System.out.println("-----------------------------------------------");
}

public void computeInterest() {
state.computeInterest();
}
}

2. 抽象状态类

2.1 AccountState

1
2
3
4
5
6
7
8
9
10
11
12
public abstract class AccountState {

protected Account account;

public abstract void deposit(double amount);

public abstract void withdraw(double amount);

public abstract void computeInterest();

public abstract void stateCheck();
}

3. 具体状态类

3.1 NormalState

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
public class NormalState extends AccountState {

public NormalState(Account account) {
this.account = account;
}

public NormalState(AccountState accountState) {
this.account = accountState.account;
}

@Override
public void deposit(double amount) {
account.setBalance(account.getBalance() + amount);
stateCheck();
}

@Override
public void withdraw(double amount) {
account.setBalance(account.getBalance() - amount);
stateCheck();
}

@Override
public void computeInterest() {
System.out.println("正常状态,无须支付利息");
}

@Override
public void stateCheck() {
if (account.getBalance() > -2000 && account.getBalance() <= 0) {
account.setState(new OverdraftState(this));
} else if (account.getBalance() <= -2000) {
account.setState(new RestrictedState(this));
};
}
}

3.2 OverdraftState

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
public class OverdraftState extends AccountState {

public OverdraftState(AccountState accountState) {
this.account = accountState.account;
}

@Override
public void deposit(double amount) {
account.setBalance(account.getBalance() + amount);
stateCheck();
}

@Override
public void withdraw(double amount) {
account.setBalance(account.getBalance() - amount);
stateCheck();
}

@Override
public void computeInterest() {
System.out.println("透支状态,需要支付利息");
}

@Override
public void stateCheck() {
if (account.getBalance() > 0) {
account.setState(new NormalState(this));
} else if (account.getBalance() <= -2000) {
account.setState(new RestrictedState(this));
}
}
}

3.3 RestrictedState

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
public class RestrictedState extends AccountState {

public RestrictedState(AccountState accountState) {
this.account = accountState.account;
}

@Override
public void deposit(double amount) {
account.setBalance(account.getBalance() + amount);
stateCheck();
}

@Override
public void withdraw(double amount) {
System.out.println("账户受限,取款失败");
}

@Override
public void computeInterest() {
System.out.println("受限状态,需要支付利息");
}

@Override
public void stateCheck() {
if (account.getBalance() > 0) {
account.setState(new NormalState(this));
} else if (account.getBalance() > -2000) {
account.setState(new OverdraftState(this));
}
}
}

4. 客户端代码

1
2
3
4
5
6
7
8
9
10
11
public class Client {
public static void main(String[] args) {
Account account = new Account("用户1", 0.0);
account.deposit(1000);
account.withdraw(2000);
account.deposit(3000);
account.withdraw(4000);
account.withdraw(1000);
account.computeInterest();
}
}

5. 优缺点及适用场景

5.1 优点

  • 封装了状态的转换规则,可以对状态转换代码进行集中管理
  • 将所有与某个状态有关的行为放到一个类中,只需要注入一个不同的状态对象即可使环境对象拥有不同的行为
  • 可以避免适用庞大的条件语句将业务方法和状态转换代码交织在一起

5.2 缺点

  • 会增加类和对象的个数,导致系统运行开销增大
  • 增加系统设计难度,状态模式的结构和实现都较为复杂
  • 对开闭原则的支持并不好,如果需要增加一个新的状态,则会导致其他状态类也要相继改变相应的源代码

5.3 适用场景

  • 对象的行为依赖于它的状态(例如某些属性值),状态的改变将导致行为的变化
  • 在代码中包含大量与对象有关的条件语句,这些条件语句的出现会导致代码的可维护性和灵活性变差

十、策略模式

排序有多种方法,如选择排序、插入排序、快速排序等,某系统要求在多种排序方案中灵活选择某一种排序方案,但不修改源代码,现使用策略模式进行设计

1. 环境类

1.1 ParamArray

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class ParamArray {

private int[] array;

private ArraySort arraySort;

public ParamArray(int[] array) {
this.array = array;
}

public void setSort(ArraySort arraySort) {
System.out.println("使用" + arraySort.getClass().getName() + "算法");
this.arraySort = arraySort;
}

public int[] sort() {
return arraySort.sort(array);
}
}

2. 抽象策略类

2.1 ArraySort

1
2
3
public interface ArraySort {
int[] sort(int[] array);
}

3. 具体策略类

3.1 InsertionSort

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
public class InsertionSort implements ArraySort{
@Override
public int[] sort(int[] array) {
// i代表待排序的索引
for(int i = 1; i < array.length; i++){
int t = array[i];
// j代表以排好序的区域索引
int j = i - 1;
while(j >= 0){
if(t < array[j]){
array[j + 1] = array[j];
} else {
// 退出循环,减少比较次数
break;
}
j--;
}
array[j + 1] = t;
}
return array;
}

public static void swap(int[] a, int i, int j){
int t = a[i];
a[i] = a[j];
a[j] = t;
}
}

3.2 QuickSort

1
2
3
4
5
6
7
public class QuickSort implements ArraySort {
@Override
public int[] sort(int[] array) {
Arrays.sort(array);
return array;
}
}

3.3 SelectionSort

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
public class SelectionSort implements ArraySort {
@Override
public int[] sort(int[] array) {
// i代表着每轮选择出来的最小元素要交换的目标索引
for (int i = 0; i < array.length - 1; i++) {
// s代表着当前最小元素的索引
int s = i;
// j的作用是遍历s之后的数,查找是否还有比s还小的数
for (int j = s + 1; j < array.length; j++) {
if (array[s] > array[j]) {
s = j;
}
}
if (s != i) {
swap(array, s, i);
}
}
return array;
}

public static void swap(int[] a, int i, int j) {
int t = a[i];
a[i] = a[j];
a[j] = t;
}
}

4. 客户端类

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Client {
public static void main(String[] args) {
int[] before = {2,3,1,5,4,7,6,9,8};
ParamArray paramArray = new ParamArray(before);
paramArray.setSort(new QuickSort());

int[] after = paramArray.sort();
System.out.println("排序后:");
for (int j : after) {
System.out.print(j + " ");
}
}
}

5. 优缺点和适用场景

5.1 优点

  • 完美支持开闭原则
  • 避免多重条件选择语句
  • 可以复用

5.2 缺点

  • 客户端必须知道所有算法或行为的情况,才方便选择
  • 可能会造成系统产生大量的策略类
  • 不支持一个策略类完成部分功能后再使用另一个策略类完成剩余功能

5.3 适用场景

  • 一个系统需要动态地选择几种处理策略
  • 避免使用难以维护的多重选择语句
  • 不希望客户端知道接触复杂的逻辑结构

十一、模板方法模式

假设每个人一天的时间花费在工作(work)和睡觉上(sleep),现有艺术家和程序员两种职业,艺术家的工作内容是绘画;程序员的工作内容是写程序。请试着使用模板方法描绘这两种职业的一天

1. 抽象类

1.1 People

1
2
3
4
5
6
7
8
9
10
11
12
13
public abstract class People {

public abstract void work();

public void sleep() {
System.out.println("工作完睡觉");
}

public void thisDay() {
work();
sleep();
}
}

2. 具体类

2.1 Artist

1
2
3
4
5
6
public class Artist extends People {
@Override
public void work() {
System.out.println("我的工作是绘画");
}
}

2.2 Programmer

1
2
3
4
5
6
public class Programmer extends People {
@Override
public void work() {
System.out.println("我的工作是写程序");
}
}

3. 客户端代码

1
2
3
4
5
6
public class Client {
public static void main(String[] args) {
People programmer = new Programmer(); // 可通过配置文件注入
programmer.thisDay();
}
}

4. 优缺点及适用场景

4.1 优点

  • 代码复用
  • 可以通过子类覆盖父类的钩子方法来决定某一步骤是否执行
  • 符合单一职责和开闭原则。通过子类来覆盖父类的基本方法,不同的子类可以提供不同的方法实现,更换和新增子类也很方便

4.2 缺点

  • 增加系统复杂度。每一个基本方法的不同实现都需要新增一个子类

4.3 适用场景

  • 对复杂的算法进行分割。延迟到子类实现
  • 可以将各子类的公共行为提取并集中到一个公共父类种,避免代码重复
  • 需要通过子类来父类算法种的某个步骤是否执行,实现了子类对父类算法的方向控制(钩子方法)

十二、访问者模式

由于访问者模式的使用条件较为苛刻,本身结构也较为复杂,因此在实际应用种使用频率不是特别高。
主要应用于 XML 文档解析、编译器的设计、复杂的集合对象处理等领域
本文不以案例展示,仅介绍其优缺点及适用场景

1. 优缺点及适用场景

1.1 优点

  • 增加新的访问操作很方便,无需修改源代码,符合开闭原则
  • 将有关元素对象的访问行为集中到一个访问者对象种,而不是分散在一个个的元素类种,使职责更加清晰
  • 访问者模式让用户能够在不修改现有元素类层次结构的情况下定义作用于该层次结构的操作

1.2 缺点

  • 增加新的元素类很困难。每增加一个新的元素类都意味着要在抽象访问者角色中增加一个新的抽象操作,并在每一个具体访问者类中新增相应的具体操作,违背了开闭原则
  • 访问者模式破坏了对象的封装性。访问者模式要求访问者对象访问并调用每一个元素对象的操作,这意味着元素对象有时候必须暴露一些自己的内部操作和内部状态,否则无法供访问者访问

1.3 适用场景

  • 一个对象结构包含多个类型的对象,希望对这些对象实施一些依赖其具体类型的操作
  • 需要对一个结构对象进行很多不同的并且不相关的操作,而需要避免让这些操作污染这些对象的类,也不希望在增加新操作时修改这些类
  • 对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!