先上headfirst的類圖和代碼:
- 具體接收者類:
public class TV {
public void on(){}
public void off(){}
public void setInputChannel(){}
public void setVolume(){}
}
public class CeilingFan {
public void high(){}
public void medium(){}
public void low(){}
public void off(){}
public void getSpeed(){}
}
public class OutDoorLight {
public void on(){}
public void off(){}
}
- 命令類
public interface Command {
public void execute();
public void undo();
}
public class LightOnCommand implements Command {
Light light;
public LightOnCommand(Light light){
this.light = light;
}
@Override
public void execute() {
light.on();
}
@Override
public void undo() {
light.off();
}
}
- 控制器類
public class RemoteControl {
Command[] onCommands;
Command[] offCommands;
// 前一個命令將被記錄在這里
Command undoCommand;
public RemoteControl() {
onCommands = new Command[7];
offCommands = new Command[7];
Command noCommand = new NoCommand();
for (int i = 0; i < 7; i++) {
onCommands[i] = noCommand;
offCommands[i] = noCommand;
}
// 一開始并沒有所謂的“前一個命令”,所以將它設置成NoCommand
undoCommand = noCommand;
}
public void setCommand(int slot, Command onCommand, Command offCommand) {
onCommands[slot] = onCommand;
offCommands[slot] = offCommand;
}
// 當按下按鈕读宙,我們?nèi)〉眠@個命令结闸,并優(yōu)先執(zhí)行它,然后將它記錄在undoCommand中扎附。
public void onButtonWasPushed(int slot){
onCommands[slot].execute();
undoCommand = onCommands[slot];
}
public void offButtonWasPushed(int slot){
offCommands[slot].execute();
undoCommand = offCommands[slot];
}
// 當按下撤銷按鈕结耀,我們調(diào)用undoCommand實例變量的undo()方法图甜,就可以倒轉前一個命令
public void undoButtonWasPushed(){
undoCommand.undo();
}
}
- 客戶端類,略
如果不使用命令模式
public class RemoteControl {
TV tv = new TV();
... //創(chuàng)建一堆實際執(zhí)行操作的類
public void onButtonWasPushed(int slot){
if(slot == 1){
tv.on();
}else if() //....一堆if else判斷
}
}
1.RemoteControl直接依賴所有具體執(zhí)行類嚼摩,高度耦合矿瘦。
2.如果現(xiàn)在需要新增或者刪除一種操作,必須修改RemoteControl潮秘,并在if else中添加或刪除一條分支枕荞,很難擴展
3.每個遙控器插槽和具體操作已經(jīng)綁死,無法在運行時變更买猖。
4.要實現(xiàn)undo不是很優(yōu)雅,雖然也可以存儲最后一次按鍵的slot和按鍵類型(on or off)來進行undo飞主,代碼將會又臭又長碌识,極難維護(因為有的比如吊扇他的轉速有五檔虱而。。魁瞪。)
5.要實現(xiàn)多種操作組合也比較困難惠呼,能想到的方式是:媽的,想不到啥能說出口的旅薄。泣崩。。
命令模式到底怎么牛逼了
- 從最前面的客戶(披薩店顧客)到命令控制者(服務員)到命令實際執(zhí)行者(廚師)凯沪,他們相互之間從來不直接說出自己的需求著洼,從頭至尾都是以命令對象(訂單)說話而叼,所以一個訂單將他們的相互依賴完全解耦葵陵。
- 上面的例子中命令可能是一個一個單獨存在并永久可以重復使用的瞻佛,也可以實現(xiàn)以隊列來存儲命令娇钱,命令控制者一個個取出來執(zhí)行文搂。
- 寫一個通用的組合式命令類秤朗,就可以無限復用實現(xiàn)任意組合的命令.
public class MultiCommand implements Command {
private Command[] commands;
public MultiCommand(Command[] commands){
this.commands = commands;
}
@Override
public void execute() {
for(Command command : commands){
command.execute();
}
}
@Override
public void undo() {
for(Command command : commands){
command.undo();
}
}
}
- 將一系列操作抽象成命令對象后取视,他能被序列化,存到日志稽物,數(shù)據(jù)庫等地方折欠,當系統(tǒng)出現(xiàn)問題時,可以將命令列表拉出來進行undo或redo傀缩,數(shù)據(jù)庫事務等赡艰。如果不用命令斤葱,每一次操作就必須保存一次原始數(shù)據(jù)快照,空間將會無比的浪費料身。
一些問題
命令對象中直接實現(xiàn)業(yè)務邏輯不行嗎衩茸,為什么需要一個接受者?其實這樣實現(xiàn)也可以幔烛,解耦成都就不如把接受者提出來了囊蓝,而且以組合接收者的方式實現(xiàn)聚霜,運行時也是可以進行動態(tài)改變的珠叔。
多層次的undo操作如何實現(xiàn)祷安?將只記錄最后一次操作的command改為使用棧實現(xiàn)兔乞,每次undo從棧頂彈出一個元素執(zhí)行undo操作即可,但是有個問題是需要控制棧的大小虱咧,無限制的往棧里添加元素一定會有問題锚国。
宏命令方式實現(xiàn)功能組合,為什么用多個command組合绘沉,不直接調(diào)用具體執(zhí)行類實現(xiàn)各自的方法豺总?擴展性太差喻喳,這樣做就相當于寫死了,如果有另外一種組合的需求就必須新增一個類來實現(xiàn)谦去,command組合以command數(shù)組傳入的形式能應付任意的組合場景而不用修改現(xiàn)有代碼蹦哼。
headfirst以及各種設計模式書籍中的例子中,reciver類執(zhí)行方法都是無參的妆丘,所以實現(xiàn)命令模式特別精簡清潔局劲,如果每一個reciver類的參數(shù)不一樣該怎么辦?首先宣脉,不能將傳參這個動作設計在execute方法里塑猖,設計在execute方法里會導致下面的場景出現(xiàn)
public interface ICommand<T> {
void execute(T parameter);
}
//或者
public interface ICommand<T> {
void execute(Object... parameter);
}
這樣一來invoke類就必須了解每個command的參數(shù)類型等邏輯谈跛,所謂的命令模式解耦就完全被破壞了,所以應該將參數(shù)放在每個command內(nèi)部蜡励,通過構造器或者setter方式設值阻桅,設值動作則由client類進行。
public class ParamterCommand implements Command {
private TV tv;
private long time; //燈開多久稽寒,參數(shù)通過構造器注入
public MultiCommand(TV tv, long time){
this.tv = tv;
}
@Override
public void execute() {
tv.on(this.time);
}
@Override
public void undo() {
//....
}
}
- 如果命令需要有返回值呢杏糙,畢竟head first書中的點餐例子中蚓土,客人下了單了,服務員通知廚師了谅河,廚師也按照菜單做菜了确丢,那做完了總得給客人吃吧? 有返回值就必須在execute方法上返回了锨天,如果返回值能抽象化(返回值都屬于同一種類型)剃毒,直接把
void execute();
改為AbstractResult execute();
即可。不能抽象調(diào)用者和接受者之間就存在比較大的耦合了益缠,調(diào)用者需要知道接收者的具體返回類型幅慌。----------------------其實命令模式更多的用處在于調(diào)用者只需要發(fā)出命令轰豆,調(diào)用者不知道具體執(zhí)行者什么時候怎么執(zhí)行齿诞,所以對于返回值也應該是這樣的:廚師做好東西后繼續(xù)棄用另外一套命令模式祷杈,之前階段的命令模式傳的是菜單渗饮,當前啟用的命令模式傳遞的是做好的一堆菜,甚至可以做好一盤上一盤私蕾。當然也可以考慮使用觀察者模式來進行后續(xù)上菜操作胡桃。
命令模式使用場景
- 遙控器例子(經(jīng)典實現(xiàn))
- 日志請求
- 工作隊列
and so on..........