命令模式主要將執行的命令, 與執行者做鬆綁的動作
沒有命令模式會是怎樣的寫法??
假如我們現在要寫一個 遙控器 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
方法負責執行完就好
或是用在日誌管理, 將執行過的動作給記錄起來, 並且若後續伺服器當機, 可將命令給復原