摘要生成中...
AI 摘要
Hunyuan-lite

这两种设计模式不太常用,因此放在一个章节中介绍。标题「用类来表现」源自《图解设计模式》介绍这命令模式、解释器模式时用的章节标题。

命令模式:命令也是类

image.png

命令模式 Command Design Pattern

The command pattern encapsulates a request as an object, thereby letting us parameterize other objects with different requests, queue or log requests, and support undoable operations.——GoF
命令模式将请求(命令)封装为一个对象,这样可以使用不同的请求参数化其他对象(将不同请求依赖注入到其他对象),并且能够支持请求(命令)的排队执行、记录日志、撤销等(附加控制)功能。

命令将可以用「物」表示,在代码实现中,就是将函数封装为对象,这也是命令模式的核心实现手段。具体来说就是设计一个包含这个函数的类,实例化一个对象传来传去,这样就可以实现把函数像对象一样使用。

image.png

当我们把函数封装成对象之后,对象就可以存储下来,方便控制执行。所以,命令模式的主要作用和应用场景,是用来控制命令的执行,比如,异步、延迟、排队执行命令、撤销重做命令、存储命令、给命令记录日志等等;再比如,要想管理工作的历史记录,只需要管理这些命令实例的集合,并可以随时再次执行过去的命令,或者将多个过去的命令整合为一个新命令。这些是命令模式能发挥独一无二作用的地方。

image.png

课本上的示例程序中,InvokerReceiver 是同一个类。Client 新建命令时,ConcreteCommand 的构造函数包含 Receiver,这样 Command 才可以调用 receiver 执行命令。

image.png

登场角色:

  • Command (命令):Command 角色负责定义命令的接口(API)。
  • ConcreteCommand (具体的命令):ConcreteCommand 角色负责实现在 Command 角色中定义的接口(API)。
  • Receiver(接收者):Receiver 角色是 Command 角色执行命令时的对象,也可以称其为命令接收者。
  • Client (请求者):Client 角色负责生成 ConcreteCommand 角色并分配 Recceiver 角色。
  • Invoker (发动者):Invoker 角色是开始执行命令的角色,它会调用在 Command 角色中定义的接口(API)。
优点 缺点
单一职责原则。可以解耦触发和执行操作的类。 代码可能会变得更加复杂,因为在发送者和接收者之间增加了一个全新的层次。
开闭原则。可以在不修改已有客户端代码的情况下在程序中创建新的命令。
可以实现撤销和恢复功能。
可以实现操作的延迟执行。
可以将一组简单命令组合成一个复杂命令。

与其他设计模式的比较

相关的设计模式:

  • 站内文章组合模式。命令的集合(它实现 Command)可以用组合模式。
  • Memento 模式。保存 Command 角色的历史记录可以使用 Memento 模式。
  • 站内文章原型模式。优势使用原型模式复制发生的事件,保存历史记录。
  • 站内文章访问者模式:可以将访问者模式视为命令模式的加强版本,其对象可对不同类的多种对象执行操作。

站内文章策略模式 很像,两者都能通过某些行为来参数化对象。不同点:

  • 在命令模式中,不同的命令具有不同的目的,对应不同的处理逻辑,并且互相之间不可替换。使用命令来将任何操作转换为对象。操作的参数将成为对象的成员变量。你可以通过转换来延迟操作的执行、将操作放入队列、保存历史命令或者向远程服务发送命令等。
  • 在策略模式中,不同的策略具有相同的目的、不同的实现、互相之间可以替换。比如,BubbleSortSelectionSort 都是为了实现排序的,只不过一个是用冒泡排序算法来实现的,另一个是用选择排序算法来实现的。

案例

例子:手游后端服务器轮询获取客户端发来的请求,获取到请求之后,借助命令模式,把请求包含的数据和处理逻辑封装为命令对象,并存储在内存队列中。然后,再从队列中取出一定数量的命令来执行。执行完成之后,再重新开始新的一轮轮询。

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 interface Command {
void execute();
}

public class GotDiamondCommand implements Command {
// 省略成员变量

public GotDiamondCommand(/*数据*/) {
//...
}

@Override
public void execute() {
// 执行相应的逻辑
}
}
//GotStartCommand/HitObstacleCommand/ArchiveCommand类省略

public class GameApplication {
private static final int MAX_HANDLED_REQ_COUNT_PER_LOOP = 100;
private Queue<Command> queue = new LinkedList<>();

public void mainloop() {
while (true) {
List<Request> requests = new ArrayList<>();

//省略从epoll或者select中获取数据,并封装成Request的逻辑,
//注意设置超时时间,如果很长时间没有接收到请求,就继续下面的逻辑处理。

for (Request request : requests) {
Event event = request.getEvent();
Command command = null;
if (event.equals(Event.GOT_DIAMOND)) {
command = new GotDiamondCommand(/*数据*/);
} else if (event.equals(Event.GOT_STAR)) {
command = new GotStartCommand(/*数据*/);
} else if (event.equals(Event.HIT_OBSTACLE)) {
command = new HitObstacleCommand(/*数据*/);
} else if (event.equals(Event.ARCHIVE)) {
command = new ArchiveCommand(/*数据*/);
} // ...一堆else if...

queue.add(command);
}

int handledCount = 0;
while (handledCount < MAX_HANDLED_REQ_COUNT_PER_LOOP) {
if (queue.isEmpty()) {
break;
}
Command command = queue.poll();
command.execute();
}
}
}
}

解释器模式:语法规则也是类

image.png

🍐⚱️:我感觉这种模式只能解决这种语言解析的问题。
🍐⚱️:单例模式简单,抽象工厂、解释器难。

解释器模式描述如何构建一个简单的「语言」解释器。

解释器模式 Interpreter Design Pattern

Interpreter pattern is used to defines a grammatical representation for a language and provides an interpreter to deal with this grammar.——GoF
解释器模式为某个语言定义它的语法(文法)表示,并定义一个解释器用来处理这个语法。

在解释器模式中,程序要解决的问题会被用非常简单的「迷你语言」表达出来,即用「迷你语言」编写的「迷你程序」把具体问题表述出来。

比起命令模式,解释器模式更加小众,只在一些特定的领域会被用到,比如编译器、规则引擎、正则表达式。

image.png

image.png

《图解设计模式》中例子值得学习。

登场角色

  • AbstractExpression(抽象表达式):AbstractExpression 角色定义了语法树节点的共同接口(API)。在示例程序中,由 Node 类扮演此角色。
  • TerminalExpression(终结符表达式):TerminalExpression 角色对应 BNF 中的终结符表达式。
  • NonterminalExpression(非终结符表达式):NonterminalExpression 角色对应 BNF 中的非终结符表达式。
  • Context(文脉、上下文):Context 角色为解释器进行语法解析提供了必要的信息。
  • Client(请求者):为了推导语法树,Client 角色会调用 TerminalExpression 角色和 NonterminalExpression 角色。

拓展思路:

  • 其他的迷你语言:正则表达式,检索表达式,批处理语言

相关的设计模式:

  • NonterminalExpression 角色多是递归结构,因此常会使用 站内文章组合模式 来实现 NonterminalExpression 角色。
  • 有时会使用 站内文章享元模式 来共享 TerminalExpression 角色。
  • 在推导出语法树后,有时会使用 Visitor 模式来访问语法树的各个节点。

本文参考