命令模式

先上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..........
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末标捺,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子嗤疯,更是在濱河造成了極大的恐慌闺兢,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件脚囊,死亡現(xiàn)場離奇詭異悔耘,居然都是意外死亡我擂,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進店門看峻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來互妓,“玉大人,你說我怎么就攤上這事澈蚌≈槿颍” “怎么了瘫辩?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵伐厌,是天一觀的道長。 經(jīng)常有香客問我军熏,道長卷扮,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任摩幔,我火速辦了婚禮或衡,結果婚禮上车遂,老公的妹妹穿的比我還像新娘。我一直安慰自己坡疼,他們只是感情好衣陶,可當我...
    茶點故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著狱意,像睡著了一般拯欧。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天隆箩,我揣著相機與錄音捌臊,去河邊找鬼兜材。 笑死,一個胖子當著我的面吹牛曙寡,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播执隧,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼户侥,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了屋摔?” 一聲冷哼從身側響起刃泌,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤耙替,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后俗扇,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡滞谢,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年狮杨,在試婚紗的時候發(fā)現(xiàn)自己被綠了到忽。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片清寇。...
    茶點故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡华烟,死狀恐怖持灰,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情喂链,我是刑警寧澤,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布衩藤,位于F島的核電站,受9級特大地震影響检诗,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜悠轩,卻給世界環(huán)境...
    茶點故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一攻泼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧何鸡,春花似錦牛欢、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至访得,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間俱笛,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工泥技, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留磕仅,地道東北人。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓店茶,卻偏偏與公主長得像,于是被迫代替她去往敵國和親贩幻。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內(nèi)容

  • 1 場景問題# 1.1 如何開機## 估計有些朋友看到這個標題會非常奇怪,電腦裝配好了憔辫,如何開機?不就是按下啟動按...
    七寸知架構閱讀 2,827評論 1 59
  • 3.5 隊列請求## 所謂隊列請求坏平,就是對命令對象進行排隊锦亦,組成工作隊列,然后依次取出命令對象來執(zhí)行坎穿。多用多線程或...
    七寸知架構閱讀 2,018評論 4 54
  • 目錄 本文的結構如下: 什么是命令模式 為什么要用該模式 模式的結構 代碼示例 優(yōu)點和缺點 適用環(huán)境 模式應用 總...
    w1992wishes閱讀 1,112評論 2 9
  • 意圖把方法調(diào)用封裝起來或將一個請求封裝成一個對象從而實現(xiàn)“對象請求者”和“對象執(zhí)行者”的解耦玲昧。對請求進行排隊篮绿、記錄...
    _chubby閱讀 272評論 0 0
  • “你怎么想起問這亲配,哎惶凝,發(fā)什么愣?”他拿筷子敲了一下我的頭苍鲜。 “我中獎了玷犹。”我怔怔的看著他說坯屿。 “做夢呢×祯耍” “我中...
    林夕楓起閱讀 323評論 0 1