將函數(shù)調用封裝成命令對象,從而支持各種后續(xù)管理:傳遞,保存,延后處理,撤銷重做等
經(jīng)典命令模式解析
命令模式的5個角色:
- Command:命令接口,定義execute/undo之類的接口方法。
- ConcreteCommand:具體的命令,實現(xiàn)命令接口。調用Receiver執(zhí)行具體命令,也可以直接執(zhí)行命令。
- Receiver:接收者,命令的實際執(zhí)行者。
- Invoker:請求者,持有命令對象,要求命令對象執(zhí)行請求。
- Client:創(chuàng)建命令對象,設置命令對象的接受者,把命令對象添加給請求者。驅動Invoker執(zhí)行請求。

命令模式的UML圖
偽代碼例子:
// 命令接口
interface Command{
execute();
}
// 具體action命令
class ConcreteCommandAction : Command{
receiver = null;
_construct(Receiver receiver){
this.receiver = receiver;
}
execute(){
this.receiver->action();
}
}
// 接收者
class Reveiver{
action(){
// do some action
}
}
// 請求者
class Invoker{
command = null;
setCommand(Command command){
this.command = command;
}
runCommand(){
this.command->execute();
}
}
// 客戶端組裝調用
client(){
receiver = getRecevier();
command = new ConcreteCommandAction(receiver);
invoker = getInvoker();
invoker->seCommand(command);
invoker->runCommand();
}
上面一堆代碼實現(xiàn)了:請求receiver執(zhí)行action。等效代碼如下:
client(){
recevier = getReceiver();
receiver->action();
}
這。。。
對命令模式的簡單分析
封裝調用,完成解耦
ConcreteCommandAction封裝了action的調用,invoker與recevier完全解耦了。
封裝調用,這個大家都喜歡用。一般用到第三方庫時,會針對性的做一層封裝,統(tǒng)一邏輯,簡化接口,屏蔽細節(jié)。這在多平臺適配時幾乎是必須的。
增加命令很方便
如果我們要給Receiver增加新的行為。
- 簡單實現(xiàn):修改Receiver的定義,增加新的方法。(違反關閉原則,Reveiver會越來越大)
- 命令模式:新加命令,在execute里實現(xiàn)新的行為。(當然新的行為要能使用Receiver的公開接口組合實現(xiàn))
如:加個新行為,按需執(zhí)行action多次,可以定義個新的Command
class ConcreteCommandRepeatAction : Command{
receiver = null;
times = 0;// 執(zhí)行次數(shù)
_construct(Receiver receiver, int times){
this.receiver = receiver;
this.times = times;
}
execute(){
while(times > 0){
this.receiver->action();
this.times -= 1;
}
}
}
這個很有誘惑力,相當于不改類定義,而給類增加新的方法。
一個極端的例子,Recevier就是個數(shù)據(jù)結構,每個具體命令操作Recevier的數(shù)據(jù)。
相當于把數(shù)據(jù)和操作分離,然后按需組合成為想要的對象。(好像面向對象就是為了把數(shù)據(jù)和操作組合在一起的)
函數(shù)調用封裝成命令對象
簡單的調用被封裝成命令對象后,有了自己的生命周期。這個有需要好處。
- 可以暫存下來,延后調用。
像網(wǎng)絡請求這類情景,經(jīng)常請求轉換成命令對象,在單獨的線程里排隊執(zhí)行。 - 可以序列化成數(shù)據(jù),進行網(wǎng)絡傳送,錄像保存。
幀同步游戲的網(wǎng)絡同步和錄像保存就可以這樣實現(xiàn)。 - 方便管理,很容易實現(xiàn)撤銷和重做。
簡單實現(xiàn)撤銷和重做
修改下接口Command
interface Command{
execute();
undo();
redo();
}
保存命令成隊列,設置記錄當前命令index
// 撤銷
command_queue[index--].undo();
// 重做
command_queue[index++].redo();

使用小結
模式的重點:
- 對函數(shù)調用做封裝,完成解耦。
- 把函數(shù)調用轉換成命令對象,相當于把調用執(zhí)行轉換成數(shù)據(jù)指令,方便后續(xù)各種管理。
不要使用的情況:
- 如果只是要解耦下,做一層調用封裝就行了。
- 想要擴展命令,多定義幾個調用分裝也就行了。
- 如果是立即調用執(zhí)行,沒必要轉成命令模式使用。
使用的情況:只在有后續(xù)管理需求時使用
- 需要延后調用,如網(wǎng)絡請求,文件加載等耗時長的任務。使用異步線程處理命令隊列比較好。
- 需要保存,做分發(fā)傳輸記錄。
- 需要撤銷重做的功能。