本博客大部分内容来于免费在线学习设计模式

1:命令模式

命令模式是一种行为设计模式, 它可将请求转换为一个包含与请求相关的所有信息的独立对象。 该转换让你能根据不同的请求将方法参数化、 延迟请求执行或将其放入队列中, 且能实现可撤销操作。

2:命令模式问题

假如你当前有一个列表页面需要渲染,当页面加载时需要请求数据,点击重置按钮时也需要请求页面数据,搜索数据也可以根据这个接口来请求数据。那么你怎么处理呢?是将数据的实现类复制多次,还是用其他方法呢?

3:命令模式解决方案

很显然,在我们的实际使用中,是将数据请求和数据实现分离。将请求的所有细节(例如调用的对象、方法名称和参数列表)抽取出来组成命令类,该类中仅包含一个用于触发请求的方法。

在现实生活中,命令模式的例子也很多。比如看电视时,我们只需要轻轻一按遥控器就能完成频道的切换,这就是命令模式,将换台请求和换台处理完全解耦了。电视机遥控器(命令发送者)通过按钮(具体命令)来遥控电视机(命令接收者)。

再比如,我们去餐厅吃饭,菜单不是等到客人来了之后才定制的,而是已经预先配置好的。这样,客人来了就只需要点菜,而不是任由客人临时定制。餐厅提供的菜单就相当于把请求和处理进行了解耦,这就是命令模式的体现。

4:命令模式结构

20201130095822

  1. 发送者(Sender——亦称 “触发者(Invoker)”——类负责对请求进行初始化,其中必须包含一个成员变量来存储对于命令对象的引用。发送者触发命令,而不向接收者直接发送请求。注意,发送者并不负责创建命令对象:它通常会通过构造函数从客户端处获得预先生成的命令。

  2. 命令 (Command) 接口通常仅声明一个执行命令的方法。

  3. 具体命令 (Concrete Commands) 会实现各种类型的请求。 具体命令自身并不完成工作, 而是会将调用委派给一个业务逻辑对象。 但为了简化代码, 这些类可以进行合并。

    接收对象执行方法所需的参数可以声明为具体命令的成员变量。 你可以将命令对象设为不可变, 仅允许通过构造函数对这些成员变量进行初始化。

  4. 接收者 (Receiver) 类包含部分业务逻辑。 几乎任何对象都可以作为接收者。 绝大部分命令只处理如何将请求传递到接收者的细节, 接收者自己会完成实际的工作。

  5. 客户端 (Client) 会创建并配置具体命令对象。 客户端必须将包括接收者实体在内的所有请求参数传递给命令的构造函数。 此后, 生成的命令就可以与一个或多个发送者相关联了。

代码示例:

模拟餐馆点餐:客户告诉服务员需要哪些早餐,服务员将请求交给厨师来解决。点早餐相当于“命令”,服务员相当于“调用者”,厨师相当于“接收者”。

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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
/**
* 抽象厨师类-能弄什么
*/
abstract class Chef {
abstract void cooking();
}

/**
* 蒸包子
*/
class BunChef extends Chef {
@Override
public void cooking() {
System.out.println("蒸包子");
}
}

/**
* 煮面条
*/
class NoodleChef extends Chef {
@Override
public void cooking() {
System.out.println("煮面条");
}
}
/**
* 煮饺子
*/
class DumplingsChef extends Chef {
@Override
void cooking() {
System.out.println("煮饺子");
}
}

/**
* 早餐类-抽象命令类:说明能干什么
*/
interface Breakfast {
abstract void cooking();
}

/**
* 具体早餐:包子
*/
class Bun implements Breakfast {
private Chef chef;

Bun() {
this.chef = new BunChef();
}

@Override
public void cooking() {
chef.cooking();
}
}

/**
* 具体早餐:面条
*/
class Noodles implements Breakfast {
private Chef chef;

Noodles() {
this.chef = new NoodleChef();
}

@Override
public void cooking() {
chef.cooking();
}
}

/**
* 具体早餐:饺子
*/
class Dumplings implements Breakfast {
private Chef chef;

Dumplings() {
this.chef = new DumplingsChef();
}

@Override
public void cooking() {
this.chef.cooking();
;
}
}

/**
* 服务员-设置有哪些早餐,就可以获得对应早餐
* @author ming
*/
class Waiter {
private Breakfast bun, noodles, dumplings;

public void chooseBun() {
bun.cooking();
}

public void setBun(Breakfast bun) {
this.bun = bun;
}

public void chooseNoodles() {
noodles.cooking();
}

public void setNoodles(Breakfast noodles) {
this.noodles = noodles;
}

public void chooseDumplings() {
dumplings.cooking();
}

public void setDumplings(Breakfast dumplings) {
this.dumplings = dumplings;
}
}

public class Action {
public static void main(String[] args) {
Breakfast bun = new Bun();
Breakfast noodles = new Noodles();
Breakfast dumplings = new Dumplings();
Waiter waiter = new Waiter();
waiter.setBun(bun);
waiter.setDumplings(dumplings);
waiter.setNoodles(noodles);

waiter.chooseBun();
waiter.chooseDumplings();
waiter.chooseNoodles();
}
}

5:命令模式适用场景

  1. 如果你需要通过操作来参数化对象,可使用命令模式。
  2. 如果你想要将操作放入队列中、 操作的执行或者远程执行操作, 可使用命令模式。
  3. 如果你想要实现操作回滚功能,可使用命令模式。

6:命令模式优缺点

优点 缺点
单一职责原则。 你可以解耦触发和执行操作的类。 代码可能会变得更加复杂, 因为你在发送者和接收者之间增加了一个全新的层次。
开闭原则。 你可以在不修改已有客户端代码的情况下在程序中创建新的命令。
你可以实现撤销和恢复功能。
你可以实现操作的延迟执行。
你可以将一组简单命令组合成一个复杂命令。

7:命令模式与其他模式关系

  • 责任链模式、命令模式、中介者模式和观察者模式用于处理请求发送者和接收者之间的不同连接方式:
    • 责任链按照顺序将请求动态传递给一系列的潜在接收者,直至其中一名接收者对请求进行处理。
    • 命令在发送者和请求者之间建立单向连接。
    • 中介者清除了发送者和请求者之间的直接连接,强制它们通过一个中介对象进行间接沟通。
    • 观察者允许接收者动态地订阅或取消接收请求。
  • 责任链的管理者可使用命令模式实现。在这种情况下,你可以对由请求代表的同一个上下文对象执行许多不同的操作。
    还有另外一种实现方式,那就是请求自身就是一个命令对象。在这种情况下,你可以对由一系列不同上下文连接而成的链执行相同的操作。
  • 你可以同时使用命令和备忘录模式来实现 “撤销”。在这种情况下,命令用于对目标对象执行各种不同的操作,备忘录用来保存一条命令执行前该对象的状态。
  • 命令和策略模式看上去很像,因为两者都能通过某些行为来参数化对象。但是,它们的意图有非常大的不同。
    • 你可以使用命令来将任何操作转换为对象。操作的参数将成为对象的成员变量。你可以通过转换来延迟操作的执行、将操作放入队列、保存历史命令或者向远程服务发送命令等。
    • 另一方面,策略通常可用于描述完成某件事的不同方式,让你能够在同一个上下文类中切换算法。
  • 原型模式可用于保存命令的历史记录。
  • 你可以将访问者模式视为命令模式的加强版本,其对象可对不同类的多种对象执行操作。

8:命令模式举例

命令模式就像一个遥控器一样,将你的各种各样的请求封装起来。命令模式经常和外观模式一起使用,外观模式提供一个访问的接口,命令模式将你的请求封装为一个命令来访问这个接口。

9:参考