命令模式主要將執行的命令, 與執行者做鬆綁的動作
沒有命令模式會是怎樣的寫法??
假如我們現在要寫一個 遙控器 RemoteControl (執行者), 上面會有很多按鈕功能 (命令)
每個按鈕可能都對應各個功能, 例如 打開, 關閉某些房間的燈
於是我們定義了 Light 抽象類別, 有著 on()與off()開關燈的方法, 並且實作了 LivingRoomLight, KitchenLight這兩個房間並繼承 Light抽象類別的方法, 使他們都有開關的功能!
接著在遙控器 RemoteControl類別內定義了 Light[] 陣列, 儲存欲執行的功能 (實作Light的類別們)
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 public class main { public static void main (String[] args) { RemoteControl remoteControl = new RemoteControl( new Light[]{ new LivingRoomLight(), new KitchenLight() }); remoteControl.turnOnLight(0 ); remoteControl.turnOnLight(1 ); remoteControl.turnOffLight(0 ); remoteControl.turnOffLight(1 ); } } class RemoteControl { Light[] lights = new Light[10 ]; RemoteControl(Light[] lights) { for (int i=0 ; i<lights.length; i++) { this .lights[i] = lights[i]; } } public void turnOnLight (int buttonNumber) { this .lights[buttonNumber].on(); } public void turnOffLight (int buttonNumber) { this .lights[buttonNumber].off(); } } abstract class Light { Light() {} abstract void on () ; abstract void off () ; } class LivingRoomLight extends Light { LivingRoomLight() {} @Override public void on () { System.out.println("Turn on the living room light" ); } @Override public void off () { System.out.println("Turn off the living room light" ); } } class KitchenLight extends Light { KitchenLight() {} @Override public void on () { System.out.println("Turn on the kitchen light" ); } @Override public void off () { System.out.println("Turn off the kitchem light" ); } }
執行結果如下
問題
假如現在又想要添加電視與冷氣開關的功能 TV, AirConditioner, 那不就又要在RemoteControl類別內進行修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class RemoteControl { HomeDevice[] homeDevices = new HomeDevice[10 ]; Light[] lights = new Light[10 ]; RemoteControl(Light[] lights) { for (int i=0 ; i<lights.length; i++) { this .lights[i] = lights[i]; } } public void turnOnLight (int buttonNumber) { this .lights[buttonNumber].on(); } public void turnOffLight (int buttonNumber) { this .lights[buttonNumber].off(); } }
可看到每次若要為遙控器類別新增功能, 都要對RemoteControl進行修改的動作, 如此一來可能會造成其他已經內嵌好的功能可能被改壞的狀況發生, 這時我們可以使用 Command 模式 來避免這種狀況發生
Command 命令模式的優勢
命令模式可以幫我們把要賦予RemoteControl的這些功能給封裝起來, 成為一個獨立的個體, 不會跟RemoteControl給耦合再一起 (像上面turnOnLight, turnOffLight等功能)
我們可以把 Light 的開關功能都視作為 Command (命令)抽象介面, 該介面包含了一個 execute()功能,
接著由Command (命令)衍伸出 LightOnCommand與LightOffCommand這兩個實作類別, 負責定義Light的開與關的動作
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 interface Command { void execute () ; } class Light { void on () { System.out.println("Turn on the light" ); }; void off () { System.out.println("Turn off the light" ); }; } class LightOnCommand implements Command { Light light; LightOnCommand() { this .light = new Light(); } public void execute () { light.on(); } } class LightOffCommand implements Command { Light light; LightOffCommand() { this .light = new Light(); } public void execute () { light.off(); } } class NoCommand implements Command { NoCommand() {} public void execute () { System.out.println("Not yet defined" ); } }
接著我們需要在遙控器類別 RemoteControl 定義可放置Command的動作 setXXXCommand(), 以及呼叫Command的 pressOnButton()與pressOffButton()
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 class RemoteControl { Command[] onCommands = new Command[7 ]; Command[] offCommands = new Command[7 ]; RemoteControl() { for (int i=0 ; i<7 ; i++) { onCommands[i] = new NoCommand(); offCommands[i] = new NoCommand(); } } public void setOnCommand (int slot, Command onCommand) { this .onCommands[slot] = onCommand; } public void setOffCommand (int slot, Command offCommand) { this .offCommands[slot] = offCommand; } public void pressOnButton (int slot) { this .onCommands[slot].execute(); } public void pressOffButton (int slot) { this .offCommands[slot].execute(); } public String toString () { StringBuffer stringBuff = new StringBuffer(); stringBuff.append("\n ------- Remote Control-------\n" ); for (int i=0 ; i < onCommands.length; i++) { stringBuff.append("[slot " + i + "] " + onCommands[i].getClass().getName() + " " + offCommands[i].getClass().getName() + "\n" ); } return stringBuff.toString(); } }
透過這樣的做法, 我們可以看到原本RemoteControl內的與Light操作有關的動作, 都被我們透過 Command 抽象介面以及其 LightOnCommand與LightOffCommand實作類別給抽離出來了
於是我們可以來執行看看
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 public class main { public static void main (String[] args) { RemoteControl remoteControl = new RemoteControl(); Light LivingRoomLight = new Light("Living Room" ); Light KitchenLight = new Light("Kitchen" ); remoteControl.setOnCommand(0 , new LightOnCommand(LivingRoomLight)); remoteControl.setOffCommand(0 , new LightOffCommand(LivingRoomLight)); remoteControl.setOnCommand(1 , new LightOnCommand(KitchenLight)); remoteControl.setOffCommand(1 , new LightOffCommand(KitchenLight)); System.out.println(remoteControl.toString()); remoteControl.pressOnButton(0 ); remoteControl.pressOffButton(0 ); remoteControl.pressOnButton(1 ); remoteControl.pressOffButton(1 ); } }
若接下來需要新增如浴室開關, 電視開關, 只要透過RemoteConrol的setCommand()方法, 即可以動態做設置, 不需更動到遙控器類別原本的程式碼! (除非要改變遙控器的slot大小了)
進階的Command Pattern技巧
等待補充 (為Command介面新增 undo()方法, 使動作復原)
應用場景
運用在任務序列中, 多執行緒只要拿到Command物件, 並且執行execute方法負責執行完就好
或是用在日誌管理, 將執行過的動作給記錄起來, 並且若後續伺服器當機, 可將命令給復原