Java設計模式(十一)
-----------命令模式Command
引入目的
爲了完成調用者和接受者之間的解耦,簡略調用者端的代碼設計,使得將來對調用者代碼維護成本大大降低。
<?xml:namespace prefix = o />
例如:調用者要增加一個控制功能,我們只需要編寫新的Command,在調用者添加該命令的引用,然後在客戶端指定好Command的具體接受者,並且爲調用者setCommand,就可以了。
定義與組成
定義
將一個請求封裝爲一個對象,從而使你可用不同的請求對客戶進行參數化;對請求排隊或記錄請求日誌,以及支持可撤消的操作。
角色
我們用一個遙控器的例子來闡述這個模式中的每一個對象。
話說有一個神奇的遙控器,上面有很多按鈕,每個按鈕都控制着一個家用電器,遙控器並不用知道這些家用電器具體是怎麼運轉的,它只需要發出命令(按動相應按鈕)即可。
好了,下面我們注意分析以下模式中的角色
抽象命令
定義了excute()和undo()方法,當然如果有需要還可以增加redo…一切需要的方法。
public interface Command { public void excute();
public void undo();
} |
具體命令
命令對象將接收者和具體對象封裝成對象,定義了接受者和動作的綁定關係,實現了抽象命令中定義的接口,這些接口實現調用了綁定接受者的具體行爲。
public class OpenWaterHeaderCommand implements Command { private WaterHeater wh; public OpenWaterHeaderCommand(WaterHeater wh) { this.wh = wh; } public void excute() { this.wh.open(); } public void undo() { this.wh.close(); } }
|
public class PlayDVDCommand implements Command { private DVD dvd; public PlayDVDCommand(DVD dvd) { this.dvd = dvd; } public void excute() { dvd.play(); } public void undo() { dvd.stop(); } }
|
public class CloseTVCommand implements Command { private TV tv; public CloseTVCommand(TV tv) { this.tv = tv; } public void excute() { tv.close(); } public void undo() { tv.open(); } } |
接收者
真正接受動作的實例,在本例中我們定義 熱水器,電視機,DVD機三個類
public class DVD { public void open() { System.out.println("打開DVD"); } public void close() { System.out.println("關閉DVD"); } public void play() { System.out.println("DVD開始播放"); } public void stop() { System.out.println("DVD停止播放"); } }
|
public class TV { public void open() { System.out.println("打開電視機"); }
public void close() { System.out.println("關閉電視機"); }
}
|
public class WaterHeater {
public void open() {
System.out.println("打開熱水器"); }
public void close() { System.out.println("關閉熱水器"); }
}
|
調用者
存儲命令對象實例,實現setCommand方法
public class RemoteController { OpenWaterHeaderCommand openWHCommand; PlayDVDCommand playDVDCommand; CloseTVCommand closeTVCommand;
private Command curCommand;
public void clickOpenWaterHeaderButton() { openWHCommand.excute(); curCommand = openWHCommand; }
public void clickCloseTVButton() { closeTVCommand.excute(); curCommand = closeTVCommand; }
public void clickPlayDVDButton() { playDVDCommand.excute(); curCommand = playDVDCommand; }
public void setOpenWHCommand(OpenWaterHeaderCommand openWHCommand) { this.openWHCommand = openWHCommand; }
public void setPlayDVDCommand(PlayDVDCommand playDVDCommand) { this.playDVDCommand = playDVDCommand; }
public void setCloseTVCommand(CloseTVCommand closeTVCommand) { this.closeTVCommand = closeTVCommand; }
public void clickUndoButton() { curCommand.undo(); } } |
客戶端
擁有調用者實例和接受者實例,可以創建命令實例,並且指定其接受者,調用setCommand方法。我們可以把Command理解成調用者和接受者的橋樑。
public class MyTest {
/** * @param args * 我們可以理解成自己(一個人)或者就是你的業務邏輯 */ public static void main(String[] args) { // TODO Auto-generated method stub //接收者 WaterHeater wh=new WaterHeater(); TV tv=new TV(); DVD dvd=new DVD();
//命令,指定接收者 OpenWaterHeaderCommand c1=new OpenWaterHeaderCommand(wh); PlayDVDCommand c2=new PlayDVDCommand(dvd); CloseTVCommand c3=new CloseTVCommand(tv);
//實例化調用者 RemoteController control=new RemoteController(); control.setOpenWHCommand(c1); control.setCloseTVCommand(c3); control.setPlayDVDCommand(c2);
//按動按鈕 control.clickCloseTVButton(); control.clickOpenWaterHeaderButton();
control.clickPlayDVDButton(); control.clickUndoButton(); } }
調用結果: 關閉電視機 打開熱水器 DVD開始播放 DVD停止播放
|
下圖就是命令模式條用關係的一個直觀總結!
模式使用流程
1.客戶創建一個命令對象
2.客戶用setCommand將命令對象存儲在調用者中(遙控器)
3.在某個時刻,客戶可以要求調用者調用這個命令。一旦命令被加載到調用者,該命令可以被加載或丟棄,也可以存儲下來多次使用。
4. 如果要實現undo操作,那麼就要在Command接口中增加undo方法,然後在調用者中記錄最後一個執行的Command對對象。如果遇到帶有多種狀態的撤銷操作,那麼我們在該Command的excute執行時,首先要記錄下這個實例變化前的狀態,在undo方法中去判斷後調用接受者不同的action。
總結
優點
1) 命令模式將調用操作的請求對象與知道如何實現該操作的接收對象解耦。
2) 具體命令角色可以被不同的請求者角色重用。
3) 你可將多個命令裝配成一個複合命令。
4) 增加新的具體命令角色很容易,因爲這無需改變已有的類。
適用環境
1) 需要抽象出待執行的動作,然後以參數的形式提供出來——類似於過程設計中的回調機
制。而命令模式正是回調機制的一個面向對象的替代品。
2) 在不同的時刻指定、排列和執行請求。一個命令對象可以有與初始請求無關的生存期。
3) 需要支持取消操作。
4) 支持修改日誌功能。這樣當系統崩潰時,這些修改可以被重做一遍。
5) 需要支持事務操作。