一、問題的產(chǎn)生
如果我們想設(shè)計(jì)一個(gè)遙控器

而需要遙控的電器,各自都有不同的類

如果把遙控器和操作電器的類捆綁在一起,如果新增電器或已有電器新增方法的話,都會(huì)改動(dòng)現(xiàn)有的設(shè)計(jì),明顯不是一個(gè)好的設(shè)計(jì),所以需要把“動(dòng)作的請(qǐng)求者”(遙控器)和“動(dòng)作的執(zhí)行者”(電器)進(jìn)行解耦。例如可以把請(qǐng)求(打開電燈)封裝成一個(gè)特定對(duì)象(客廳電燈對(duì)象),當(dāng)遙控器按鈕被按下時(shí),請(qǐng)客廳電燈去做相關(guān)的工作,而遙控器是不需要知道工作內(nèi)容是什么的,這樣遙控器就和電器解耦了。
二、簡化問題
餐廳是如何工作的呢?

細(xì)化一下

這樣可以看出餐廳的工作也是一個(gè)命令模式的例子,不同的客戶創(chuàng)建不同的訂單createOrder(),女招待拿到訂單takeOrder(),把它交給廚師,也就是給廚師下了個(gè)命令orderUp(),要做菜了。所以女招待和廚師之間是解耦的,女招待的訂單封裝了餐點(diǎn)的細(xì)節(jié),她只要調(diào)用每個(gè)訂單的方法即可,而廚師看了訂單就知道該做什么菜,廚師和女招待之間不需要直接溝通。
把上面的餐廳流程圖改成命令模式

把餐廳的工作過程用代碼表現(xiàn)一下吧
1、實(shí)現(xiàn)命令接口
首先,讓所有的命令對(duì)象實(shí)現(xiàn)相同的包含一個(gè)方法的接口,就是orderUp()(讓廚師開始做菜),一般慣用的名稱為execute()
public interface Command {
public void execute();
}
2、實(shí)現(xiàn)做菜的命令
//這是一個(gè)命令,所以需要實(shí)現(xiàn)Command接口
public class CookCommand implements Command {
Beef beef;
//構(gòu)造器被傳入了牛肉,以便讓這個(gè)命令控制,然后記錄在實(shí)例變量中
//一旦調(diào)用execute(),就有這個(gè)beef稱為接受者,負(fù)責(zé)接受請(qǐng)求
public CookCommand (Beef beef) {
this.beef = beef;
}
//這個(gè)execute()方法調(diào)用接收對(duì)象(beef)的cook()方法
public void execute() {
beef.cook();
}
}
3、使用命令對(duì)象
//女招待持有這個(gè)命令,這個(gè)命令控制著廚師
public class Waitress {
Command chef;
//用于設(shè)置女招待控制的命令
public void setCommand(Command command) {
chef = command;
}
//當(dāng)女招待把訂單放到廚師窗口時(shí),就調(diào)用這個(gè)方法
public void startCook() {
chef.execute();
}
}
4、測試一下
//命令模式的客戶
public class Custom {
public static void main(String[] args) {
//女招待就是調(diào)用者,會(huì)傳入一個(gè)命令對(duì)象,用于發(fā)出請(qǐng)求
Waitress waitress = new Waitress();
//創(chuàng)建了一塊牛肉,也就是請(qǐng)求的接受者
Beef beef = new Beef();
//創(chuàng)建一個(gè)命令,將接收者傳給它
CookCommand cook = new CookCommand(beef);
//把命令傳給調(diào)用者
waitress.setCommand(cook);
//請(qǐng)求做菜
waitress.startCook();
}
}
三、命令模式
1、定義
命令模式將“請(qǐng)求”封裝成對(duì)象,以便使用不同的請(qǐng)求、隊(duì)列或日志來參數(shù)化其他對(duì)象,命令模式也支持可撤銷的操作。
也就是說一個(gè)命令對(duì)象通過在特定接受者上綁定一組動(dòng)作來封裝一個(gè)請(qǐng)求,要達(dá)到這一點(diǎn),命令對(duì)象必須將動(dòng)作和接收者都包進(jìn)對(duì)象中,而對(duì)象只暴露出一個(gè)execute()方法,當(dāng)此方法被調(diào)用時(shí),接收者就會(huì)進(jìn)行這些動(dòng)作。從外部看,其他對(duì)象不知道是哪個(gè)接收者進(jìn)行了什么動(dòng)作,只知道如果調(diào)用了execute()方法,就可以發(fā)送請(qǐng)求。
2、類圖

四、完成遙控器的設(shè)計(jì)
將遙控器的每個(gè)插槽,對(duì)應(yīng)一個(gè)命令,這樣遙控器就成為調(diào)用者,按下按鈕是,相應(yīng)命令的對(duì)象的execute()方法就會(huì)被調(diào)用,也就是接收者(電燈,電扇,音響等)的動(dòng)作被調(diào)用。

1、實(shí)現(xiàn)遙控器
public class RemoteControl {
//要處理多個(gè)電器的命令,使用數(shù)組進(jìn)行記錄
Command[] onCommands;
//初始化開數(shù)組
public RemoteControl() {
onCommands = new Command[7];
Command noCommand = new NoCommand();
for(int i = 0; i < 7; i++) {
onCommands[i] = noCommand();
}
}
//命令設(shè)置需要3個(gè)參數(shù)--按鈕位置,開命令
public void setCommand(int slot, Command onCommand) {
onCommands[slot] = onCommand;
}
//當(dāng)按下開按鈕時(shí),就會(huì)調(diào)用相應(yīng)的方法
public void onButtonWasPushed(int slot) {
onCommands[slot].execute();
}
}
2、實(shí)現(xiàn)命令(打開音響)
pubic class StereoOnWithCDCommand implements Command {
Stereo stereo;
public StereoOnWithCDCommand (Stereo stereo) {
this.stereo= stereo;
}
public void execute() {
stereo.on();
stereo.setCD();
stereo.setVolume(11);
}
}
3、測試一下
public class RemoteLoader {
public static void main(String[] args) {
RemoteControl remoteControl = new RemoteControl();
//創(chuàng)建裝置在合適的位置上
Light livingRoomLight = new Light(“Living Room”);
Stereo stereo = new Stereo(“Living Room”);
...
//創(chuàng)建音響的命令
StereoOnWithCDCommand stereoOnWithCD = new StereoOnWithCDCommand(stereo);
...
//把命令加載到遙控器中
remoteControl.setCommand(0, stereoOnWithCD);
...
//按下按鈕
remoteControl.onButtonWasPushed(0);
...
}
}
遙控器的類圖

五、命令模式的其他用途
1、撤銷操作
使用一個(gè)堆棧記錄操作過程的每一個(gè)命令,當(dāng)按下撤銷按鈕時(shí),從堆棧中取出最上層的命令,調(diào)用他的撤銷方法。
2、隊(duì)列請(qǐng)求(日常安排,線程池,工作隊(duì)列)
一個(gè)工作隊(duì)列,在一端添加命令,另一端是線程,線程從隊(duì)列中取出一個(gè)命令,調(diào)用她的execute()方法,等這個(gè)調(diào)用完成后,將此命令丟棄,再取出下一個(gè)命令。工作隊(duì)列類和進(jìn)行計(jì)算的對(duì)象之間是完全解耦的,此刻線程可能在進(jìn)行財(cái)務(wù)運(yùn)算,下一刻卻在讀取網(wǎng)絡(luò)數(shù)據(jù),工作隊(duì)列對(duì)象不關(guān)心在做什么,只是取出命令對(duì)象,然后調(diào)用它的execute()方法。Android中的Runnable,Thread就是命令模式
3、日志請(qǐng)求
一些應(yīng)用需要將所有的動(dòng)作都記錄在日志中,并能在系統(tǒng)死機(jī)后,重新調(diào)用這些動(dòng)作恢復(fù)到之前的狀態(tài),新增store(),load()方法,命令模式就能支持了。當(dāng)執(zhí)行命令時(shí),將歷史記錄存儲(chǔ)在磁盤中,一旦系統(tǒng)死機(jī),就可以將命令對(duì)象重新加載,并成批的調(diào)用這些對(duì)象的execute()方法。