Java 設計模式中的命令模式詳情
模式介紹
- 命令模式(Command Pattern) :在軟件設計中,我們經常需要向某些對象發送請求,但是並不知道請求的接收者是誰,也不知道被請求的操作是哪個,我們隻需在程序運行時指定具體的請求接收者即可,此時,可以使用命令模式來進行設計。
- 命名模式使得請求發送者與請求接收者消除彼此之間的耦合,讓對象之間的調用關系更加靈活,實現解耦。
- 在命名模式中,會將一個請求封裝為一個對象,以便使用不同參數來表示不同的請求(即命名),同時命令模式也支持可撤銷的操作。
UML類圖
類圖解析:
- Invoker:是調用者角色。
- Command:是命令角色,需要執行的所有命令都在這裡,可以是接口或抽象類
- ConcreteCommand:將一個接受者對象與一個動作綁定,調用接受者相應的操作,實現execute、undo方法
- Receiver:接受者角色,知道如何實施和執行一個請求相關的操作
命令模式案例
案例解析:智能傢居,通過一個遙控器控制傢裡的智能設備
Command接口類
public interface Command { /** * 執行操作 */ void execute(); /** * 撤銷操作 */ void undo(); }
LightReceiver、CurtainReceiver信號接收者
public class LightReceiver { public void on() { System.out.println("開燈..."); } public void off() { System.out.println("關燈...."); } }
public class CurtainReceiver { public void on() { System.out.println("打開窗簾..."); } public void off() { System.out.println("關閉窗簾..."); } }
Command具體實現子類CurtainOnCommand、CurtainOffCommand、LightOnCommand、LightOffCommand、NoCommand
public class CurtainOnCommand implements Command { private CurtainReceiver curtain; public CurtainOnCommand(CurtainReceiver curtainReceiver) { this.curtain = curtainReceiver; } @Override public void execute() { curtain.on(); } @Override public void undo() { curtain.off(); } }
public class CurtainOffCommand implements Command { private CurtainReceiver curtainReceiver; public CurtainOffCommand(CurtainReceiver curtainReceiver) { this.curtainReceiver = curtainReceiver; } @Override public void execute() { curtainReceiver.off(); } @Override public void undo() { curtainReceiver.on(); } }
public class LightOnCommand implements Command { // 聚合LightReceiver private LightReceiver light; public LightOnCommand(LightReceiver light) { this.light = light; } @Override public void execute() { light.on(); } @Override public void undo() { light.off(); } }
public class LightOffCommand implements Command { // 聚合LightReceiver private LightReceiver light; public LightOffCommand(LightReceiver light) { this.light = light; } @Override public void execute() { light.off(); } @Override public void undo() { light.on(); } }
/** * 用於初始化每個按鈕 */ public class NoCommand implements Command { @Override public void execute() { System.out.println("do nothing..."); } @Override public void undo() { System.out.println("do nothing..."); } }
RemoteController調用者
public class RemoteController { // 開命令數組 private Command[] onCommands; // 關命令數組 private Command[] offCommands; // 撤銷命令位置 private int no; // 撤銷命令是否為按下命令 private Boolean isOn; /** * 初始化 */ public RemoteController() { this.onCommands = new Command[5]; this.offCommands = new Command[5]; for (int i = 0; i < 5; i++) { onCommands[i] = new NoCommand(); offCommands[i] = new NoCommand(); } } /** * 設置一行中的按鈕 * @param no * @param onCommand * @param offCommand */ public void setCommand(int no,Command onCommand,Command offCommand) { onCommands[no] = onCommand; offCommands[no] = offCommand; } /** * 開按鈕按下 * @param no */ public void onButtonPushed(int no){ // 調用按鈕方法 onCommands[no].execute(); // 記錄撤銷按鈕 this.no = no; isOn = true; } /** * 關按鈕按下 * @param no */ public void offButtonPushed(int no) { // 調用按鈕方法 offCommands[no].execute(); // 記錄撤銷按鈕 this.no = no; isOn = false; } public void undo() { if (isOn) { onCommands[no].undo(); } else { offCommands[no].undo(); } isOn = !isOn; } }
Client測試類
public class Client { public static void main(String[] args) { // 接收者 LightReceiver lightReceiver = new LightReceiver(); CurtainReceiver curtainReceiver = new CurtainReceiver(); // 命令 Command lightOnCommand = new LightOnCommand(lightReceiver); Command lightOffCommand = new LightOffCommand(lightReceiver); Command curtainOnCommand = new CurtainOnCommand(curtainReceiver); Command curtainOffCommand = new CurtainOffCommand(curtainReceiver); // 執行者 RemoteController remoteController = new RemoteController(); remoteController.setCommand(0,lightOnCommand,lightOffCommand); remoteController.setCommand(1,curtainOnCommand,curtainOffCommand); System.out.print("開燈按鈕\t"); remoteController.onButtonPushed(0); System.out.print("撤銷按鈕\t"); remoteController.undo(); System.out.print("撤銷按鈕\t"); remoteController.undo(); System.out.print("關燈按鈕\t"); remoteController.offButtonPushed(0); System.out.println("------------------"); System.out.print("開窗簾按鈕\t"); remoteController.onButtonPushed(1); System.out.print("撤銷按鈕 \t"); remoteController.undo(); System.out.print("撤銷按鈕 \t"); remoteController.undo(); System.out.print("關窗簾按鈕\t"); remoteController.offButtonPushed(1); } }
測試結果:
個人優化方案(僅供參考):
命令模式的註意事項和細節
- 將發起請求的對象與執行請求的對象解耦。發起請求的對象是調用者,調用者隻要調用命令對象的execute(方法就可以讓接收者工作,而不必知道具體的接收者對象是誰、是如何實現的,命令對象會負責讓接收者執行請求的動作,也就是說:”請求發起者”和“請求執行者”之間的解耦是通過命令對象實現的,命令對象起到瞭紐帶橋梁的作用。
- 容易設計一個命令隊列。隻要把命令對象放到列隊,就可以多線程的執行命令。
- 容易實現對請求的撤銷和重做。
- 命令模式不足:可能導致某些系統有過多的具體命令類,增加瞭系統的復雜度,這點在在使用的時候要註意。
- 空命令也是一種設計模式,它為我們省去瞭判空的操作。在上面的實例中,如果沒有用空命令,我們每按下一個按鍵都要判空,這給我們編碼帶來一定的麻煩。
- 命令模式經典的應用場景:界面的一個按鈕都是一條命令、模擬CMD (DOS命令)訂單的撤銷/恢復、觸發-反饋機制。