命令模式
命令模式(Command)将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化,对请求排队或记录请求日志。以及支持可撤销的操作。
如果没有明白的话,请允许我使用下面这张图解释
Command(抽象命令类):抽象出命令对象,可以根据不同的命令类型。写出不同的实现类
ConcreteCommand(具体命令类):实现了抽象命令对象的具体实现,当命令种类较多的情况下会导致具体的命令类数量过多
Invoker(调用者、请求者):请求的发送者,它通过命令对象来执行请求。
Receiver(接收者、执行者):接收者执行与请求相关的操作,真正执行命令的对象
Client(客户端):组装代码逻辑进行测试
总结为一句话:通过请求者(invoker)调用命令对象(command),命令对象中调用了命令具体执行者(Receiver)
# 代码实现
核心代码单元
unit UnitCommand;
interface
type
// 接收者:真正执行命令的对象
TReceiver = class
public
procedure Action();
end;
// 命令的抽象类,用接口更好
TCommand = class abstract
public
// 调用命令
procedure execute(); virtual; abstract;
end;
// 具体命令
TConcreteCommand = class(TCommand)
private
FReceiver: TReceiver;
public
property Receiver: TReceiver read FReceiver write FReceiver;
// 重写
procedure execute(); override;
// 构造
constructor Create(Receiver: TReceiver); overload;
end;
// 请求者/调用者:发起执行命令请求的对象
TInvoker = class
private
FCommand: TCommand;
public
constructor Create(Command: TCommand); overload;
procedure Call();
end;
implementation
{ TReceiver }
procedure TReceiver.Action;
begin
Writeln('命令执行了');
end;
{ TConcreteCommand }
constructor TConcreteCommand.Create(Receiver: TReceiver);
begin
self.FReceiver := Receiver;
end;
procedure TConcreteCommand.execute;
begin
Receiver.Action();
end;
{ TInvoker }
procedure TInvoker.Call;
begin
// 请求者调用命令对象执行命令的那个execute方法
FCommand.execute();
end;
constructor TInvoker.Create(Command: TCommand);
begin
self.FCommand := Command;
end;
end.
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
测试代码
program Command;
{$APPTYPE CONSOLE}
{$R *.res}
// 命令模式
uses
System.SysUtils,
UnitCommand in 'UnitCommand.pas';
begin
try
//通过请求者(invoker)调用命令对象(command),命令对象中调用了命令具体执行者(Receiver)
var
ConcreteCommand := TConcreteCommand.Create(TReceiver.Create());
var
Invoker := TInvoker.Create(ConcreteCommand);
Invoker.Call;
except
on E: Exception do Writeln(E.ClassName, ': ', E.Message);
end;
Readln;
end.
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
# 案例代码
在查找资料的时候看到很多都是以餐馆点餐为例进行的说明,我这里做一个套用。
需求:小黑去吃饭
分析:
小黑为发起请求的一方,确定餐品种类交给服务员
服务员用于连接厨师和客户。而其手中的菜单即为待执行的命令清单,服务员本身并不需要关心饭是怎么做好的,同时小黑可以吃的种类很多
厨师在接收到命令清单之后开始做饭(当然饭的种类不同做法也不同)
代码实现
unit UnitOrderingFood;
interface
uses System.Generics.Collections;
type
// 餐品指令
TFoodCommand = class abstract
public
// 餐品的制作方法,因为每一种都不同所以为抽象
procedure Make(); virtual; abstract;
end;
// 命令的接收者(命令的执行者)水饺师
TDumplingsReceiver = class
public
procedure Action();
end;
// 馄饨师(虽然不知道有没有这种职业)
TWontonReceiver = class
public
procedure Action();
end;
// 具体餐品指令水饺
TDumplingsCommand = class(TFoodCommand)
private
FDumplingsReceiver: TDumplingsReceiver;
public
procedure Make(); override;
constructor Create(DumplingsReceiver: TDumplingsReceiver); overload;
end;
// 具体餐品指令馄饨
TWontonCommand = class(TFoodCommand)
private
FWontonReceiver: TWontonReceiver;
public
procedure Make(); override;
constructor Create(WontonReceiver: TWontonReceiver); overload;
end;
TFoodInvoker = class
private
// 指令集,可以理解为菜单
FFoodCommands: TList<TFoodCommand>;
public
// 添加到指令集中
procedure SetFoodCommand(FoodCommand: TFoodCommand);
// 执行指令集中的指令
procedure Execute();
// 初始化指令集
constructor Create(); overload;
end;
implementation
{ TDumplings }
constructor TDumplingsCommand.Create(DumplingsReceiver: TDumplingsReceiver);
begin
self.FDumplingsReceiver := DumplingsReceiver;
end;
procedure TDumplingsCommand.Make;
begin
Writeln('手工水饺');
self.FDumplingsReceiver.Action();
end;
{ TWonton }
constructor TWontonCommand.Create(WontonReceiver: TWontonReceiver);
begin
self.FWontonReceiver := WontonReceiver;
end;
procedure TWontonCommand.Make;
begin
Writeln('皮薄馅大金牌馄饨');
self.FWontonReceiver.Action();
end;
{ TDumplingsReceiver }
procedure TDumplingsReceiver.Action;
begin
Writeln('和面');
Writeln('调馅');
Writeln('制成');
end;
{ TWontonReceiver }
procedure TWontonReceiver.Action;
begin
Writeln('和面');
Writeln('调馅');
Writeln('擀皮');
end;
{ TFoodInvoker }
constructor TFoodInvoker.Create;
begin
FFoodCommands := TList<TFoodCommand>.Create();
end;
procedure TFoodInvoker.Execute;
begin
for var FoodCommand in FFoodCommands do begin
FoodCommand.Make();
end;
end;
procedure TFoodInvoker.SetFoodCommand(FoodCommand: TFoodCommand);
begin
FFoodCommands.Add(FoodCommand);
end;
end.
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
测试代码
program Command;
{$APPTYPE CONSOLE}
{$R *.res}
// 命令模式
uses
System.SysUtils,
UnitOrderingFood in 'UnitOrderingFood.pas';
begin
try
// 选择菜品(构建命令并指定命令的接收者)
var
WontonCommand := TWontonCommand.Create(TWontonReceiver.Create);
var
DumplingsCommand := TDumplingsCommand.Create(TDumplingsReceiver.Create);
// 服务员生成菜单
var
FoodInvoker := TFoodInvoker.Create();
FoodInvoker.SetFoodCommand(WontonCommand);
FoodInvoker.SetFoodCommand(DumplingsCommand);
// 大师傅开始做菜
FoodInvoker.Execute();
except
on E: Exception do Writeln(E.ClassName, ': ', E.Message);
end;
Readln;
end.
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
# 小结一下
命令模式的优点如下
通过引入中间件(抽象接口)降低系统的耦合度。
扩展性良好,增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类
可以在现有命令的基础上,增加额外功能。比如日志记录,结合装饰器模式会更加灵活。
命令模式的优点如下
可能产生大量具体的命令类。因为每一个具体操作都需要设计一个具体命令类,这会增加系统的复杂性。我只写了水饺和馄饨而已。。。
增加了理解上的难度,不过这也是设计模式的通病
代码有点长,我也没办法。只要写代码一般都会很长。。。。