命令设计模式使用详解

工欲善其事,必先利其器,生活如此,写代码亦是如此,善于在代码中特有场景使用相应设计模式,会让你的代码逻辑性、可读性更强,而且当别人读起你的代码的时候会种赏心悦目的快感,下面就让我们来领略一下命令设计模式的风采吧!

命令设计模式的概念及定义:

正如大话设计模式中所言:将一个请求封装为一个对象,从而使你可用不同的对象对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作【DP】

大部分对此不了解的人看到这里内心是懵的,如此官方的定义肯定是不容易理解,下面我们就来详解一下什么是命令设计模式,所有的语言都没有图来的更直接,直接上图说明。命令模式结构图:

结构图

场景模拟

接下来我们通过某些模拟场景和模拟代码来帮助同学们进一步理解该模式(场景引用于《大话设计模式》)

1. 场景1-烧烤摊: 同学A和同学B来烧烤摊吃烧烤,卖烧烤的是一个老头,有的人要1串多辣椒,有的人要10串少辣椒,有的人先来,有的人后来,有的人给钱了,有的人没给钱,于是老头乱套了。

该场景结构图:
场景1结构图
用代码实现:

1
2
3
4
5
6
7
8
9
10
11
public class Barcuer {
static final String TAG = "Barcuer";
public void bakeMutton() {
Log.d(TAG, "烤羊肉");
}
public void bakeChicken() {
Log.d(TAG, "烤鸡肉");
}
}

1
2
3
4
5
6
7
public static void main(String[] args) {
Barcuer bc = new Barcuer();
bc.bakeMutton();
bc.bakeMutton();
bc.bakeChicken();
bc.bakeChicken();
}

这样的实现使客户端和”烤串老头”紧耦合,尽管简单,当却很僵化不利于扩展。如用户多了,请求多了就很容易乱。
那我们用场景2去优化它。

2.场景2-烧烤店:同学A和同学B来到一家烧烤店,招呼服务员说,10串羊肉串,2串鸡翅,鸡翅少辣椒,2杯可乐,然后服务员下了订单,加了备注(少辣椒)就通知后台厨师做了。

场景2相比场景1,不管你有多少人,点多少菜,有什么要求,服务员按照排队次序一一记录,最后根据订单收费,不会出现场景1中出现乱套的问题。

我们分析一下场景2:

同学A、B根本不用厨师是怎么做的,甚至都不会见到厨师,有什么要求通知服务员,让服务员通知厨师执行就行。而我们点10串羊肉串是一个命令,2串鸡翅又是一个命令,因此我们需要抽象出来一个公共的命令类。相比场景1,2中多了一个服务员的角色。服务员的作用就是接受客人的请求,通知后台厨师执行请求。

同样先上场景2的结构图:

场景2结构图

这样的图就和我们的命令设计模式非常接近了。
场景2代码步骤:

1. 创建Barcuer类,具体的行为执行者(如上场景1代码)

2. 创建公共的命令类,所有命令类的基类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public abstract class ICommand {
protected Barcuer mReceiver;
protected int mNumber;//命令号
public ICommand(Barcuer receiver) {
mReceiver = receiver;
}
public ICommand(Barcuer receiver, int number) {
mReceiver = receiver;
mNumber = number;
}
public abstract void excute();
}

该基类中持有Barcuer的对象引用,在构造函数中初始化,number是给每个命令添加的编码,以便区分,下文的number亦是如此。

3. 创建具体的烤羊肉串的命令类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class BakeMuttonCommand extends ICommand {
public BakeMuttonCommand(Barcuer receiver) {
super(receiver);
}
public BakeMuttonCommand(Barcuer receiver, int number) {
super(receiver, number);
}
@Override public void excute() {
mReceiver.bakeMutton();
}
}

mReceiver.bakeMutton(); 厨师调用真正烤羊肉串的方法

4. 创建具体的烤鸡翅的命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ChickenCommand extends ICommand {
public ChickenCommand(Barcuer receiver) {
super(receiver);
}
public ChickenCommand(Barcuer receiver, int number) {
super(receiver, number);
}
@Override public void excute() {
mReceiver.bakeChicken();
}
}

mReceiver.bakeChicken();同理厨师调用真正烤鸡翅的方法

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
32
33
34
35
36
37
38
39
40
41
42
43
public class Waiter {
final String TAG = "Waiter";
private ICommand mICommand;
private List<ICommand> mICommands;
private List<String> commandLog;
public Waiter() {
//初始化命令池
mICommands = new ArrayList<>();
commandLog = new ArrayList<>();
}
//添加命令
public void addCommand(ICommand ICommand) {
mICommands.add(ICommand);
commandLog.add("增加命令==》" + ICommand.mNumber);
}
//撤销命令
public void backCommand(ICommand ICommand) {
if (mICommands.contains(ICommand)) {
mICommands.remove(ICommand);
commandLog.add("撤销命令==》" + ICommand.mNumber);
} else {
Log.d(TAG, "命令池中没有该条命令!");
//TODO 错误处理
}
}
//通知执行全部命令
public void NotifyAll() {
for (ICommand c : mICommands) {
commandLog.add("命令" + c.mNumber + "正在执行...");
c.excute();
}
}
public void printCommandLog() {
for (String s : commandLog) {
Log.d(TAG, "记录操作====》" + s);
}
}
}

在该类中实现了命令增加和撤销、批量执行 、行为日志记录的功能。
上面的日志记录只是以集合的形式保存,在实际使用中可以以文件、数据库等持久化方式保存

6. 客户端中调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void main(String[] args) {
//真正的行为执行对象(文中的烧烤老头、厨师)
Barcuer br = new Barcuer();
//烤羊肉串的具体命令对象 0是命令编号(只是方便对命令区分)
BakeMuttonCommand bc = new BakeMuttonCommand(br, 0);
//烤羊鸡翅的具体命令对象 1是命令编号
ChickenCommand cc = new ChickenCommand(br, 1);
//通知者要通知执行对象执行命令(通知出事烤串)
Waiter invoke = new Waiter();
//服务员请求烤羊肉串的命令 下面同理
invoke.addCommand(bc);
invoke.addCommand(cc);
invoke.backCommand(cc);
//服务员通知厨师执行命令
invoke.NotifyAll();
//输入命令相关日志
invoke.printCommandLog();
}

执行效果图:
效果图

方式说明

上文以代码、结构图、运行效果相结合的方式向同学们简单阐述的什么是命令模式,相信大家对此都了解了。那么命令模式有什么作用呢,下面我们简单概述一下。

命令模式的作用

  • 很容易的实现一个队列
  • 可以比较容易将命令记录日志
  • 可以实现对请求的撤销和重做
  • 允许接收方是否要接受改命令
  • 能轻易的增加新的具体的命令类,有很强的扩展性
  • 把请求一个操作的对象与实际执行操作的对象分离,实现代码松耦合

写在结尾:

接下来的理解还得靠大家们在实战中去理解和领悟,这里只是做一个简单阐述和说明,帮助大家对此有个大概的理解。

本文结束,谢谢阅读!