命令模式

先上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;
    // 前一個(gè)命令將被記錄在這里
    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;
        }
        // 一開始并沒(méi)有所謂的“前一個(gè)命令”,所以將它設(shè)置成NoCommand
        undoCommand = noCommand;
    }

    public void setCommand(int slot, Command onCommand, Command offCommand) {
        onCommands[slot] = onCommand;
        offCommands[slot] = offCommand;
    }

    // 當(dāng)按下按鈕,我們?nèi)〉眠@個(gè)命令,并優(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];
    }
    
    // 當(dāng)按下撤銷按鈕,我們調(diào)用undoCommand實(shí)例變量的undo()方法,就可以倒轉(zhuǎn)前一個(gè)命令
    public void undoButtonWasPushed(){
        undoCommand.undo();
    }
}
  • 客戶端類,略

如果不使用命令模式

public class RemoteControl {
    TV tv = new TV();
    ... //創(chuàng)建一堆實(shí)際執(zhí)行操作的類
    public void onButtonWasPushed(int slot){
        if(slot == 1){
            tv.on();
        }else if() //....一堆if  else判斷
    }
}

1.RemoteControl直接依賴所有具體執(zhí)行類,高度耦合。
2.如果現(xiàn)在需要新增或者刪除一種操作,必須修改RemoteControl,并在if else中添加或刪除一條分支,很難擴(kuò)展
3.每個(gè)遙控器插槽和具體操作已經(jīng)綁死,無(wú)法在運(yùn)行時(shí)變更。
4.要實(shí)現(xiàn)undo不是很優(yōu)雅,雖然也可以存儲(chǔ)最后一次按鍵的slot和按鍵類型(on or off)來(lái)進(jìn)行undo,代碼將會(huì)又臭又長(zhǎng),極難維護(hù)(因?yàn)橛械谋热绲跎人霓D(zhuǎn)速有五檔。。。)
5.要實(shí)現(xiàn)多種操作組合也比較困難,能想到的方式是:媽的,想不到啥能說(shuō)出口的。。。

命令模式到底怎么牛逼了

  • 從最前面的客戶(披薩店顧客)到命令控制者(服務(wù)員)到命令實(shí)際執(zhí)行者(廚師),他們相互之間從來(lái)不直接說(shuō)出自己的需求,從頭至尾都是以命令對(duì)象(訂單)說(shuō)話,所以一個(gè)訂單將他們的相互依賴完全解耦。
  • 上面的例子中命令可能是一個(gè)一個(gè)單獨(dú)存在并永久可以重復(fù)使用的,也可以實(shí)現(xiàn)以隊(duì)列來(lái)存儲(chǔ)命令,命令控制者一個(gè)個(gè)取出來(lái)執(zhí)行。
  • 寫一個(gè)通用的組合式命令類,就可以無(wú)限復(fù)用實(shí)現(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();
        }
    }
}
  • 將一系列操作抽象成命令對(duì)象后,他能被序列化,存到日志,數(shù)據(jù)庫(kù)等地方,當(dāng)系統(tǒng)出現(xiàn)問(wèn)題時(shí),可以將命令列表拉出來(lái)進(jìn)行undo或redo,數(shù)據(jù)庫(kù)事務(wù)等。如果不用命令,每一次操作就必須保存一次原始數(shù)據(jù)快照,空間將會(huì)無(wú)比的浪費(fèi)。

一些問(wèn)題

  • 命令對(duì)象中直接實(shí)現(xiàn)業(yè)務(wù)邏輯不行嗎,為什么需要一個(gè)接受者?其實(shí)這樣實(shí)現(xiàn)也可以,解耦成都就不如把接受者提出來(lái)了,而且以組合接收者的方式實(shí)現(xiàn),運(yùn)行時(shí)也是可以進(jìn)行動(dòng)態(tài)改變的。

  • 多層次的undo操作如何實(shí)現(xiàn)?將只記錄最后一次操作的command改為使用棧實(shí)現(xiàn),每次undo從棧頂彈出一個(gè)元素執(zhí)行undo操作即可,但是有個(gè)問(wèn)題是需要控制棧的大小,無(wú)限制的往棧里添加元素一定會(huì)有問(wèn)題。

  • 宏命令方式實(shí)現(xiàn)功能組合,為什么用多個(gè)command組合,不直接調(diào)用具體執(zhí)行類實(shí)現(xiàn)各自的方法?擴(kuò)展性太差,這樣做就相當(dāng)于寫死了,如果有另外一種組合的需求就必須新增一個(gè)類來(lái)實(shí)現(xiàn),command組合以command數(shù)組傳入的形式能應(yīng)付任意的組合場(chǎng)景而不用修改現(xiàn)有代碼。

  • headfirst以及各種設(shè)計(jì)模式書籍中的例子中,reciver類執(zhí)行方法都是無(wú)參的,所以實(shí)現(xiàn)命令模式特別精簡(jiǎn)清潔,如果每一個(gè)reciver類的參數(shù)不一樣該怎么辦?首先,不能將傳參這個(gè)動(dòng)作設(shè)計(jì)在execute方法里,設(shè)計(jì)在execute方法里會(huì)導(dǎo)致下面的場(chǎng)景出現(xiàn)

public interface ICommand<T> {
    void execute(T parameter);
}
//或者
public interface ICommand<T> {
    void execute(Object... parameter);
}

這樣一來(lái)invoke類就必須了解每個(gè)command的參數(shù)類型等邏輯,所謂的命令模式解耦就完全被破壞了,所以應(yīng)該將參數(shù)放在每個(gè)command內(nèi)部,通過(guò)構(gòu)造器或者setter方式設(shè)值,設(shè)值動(dòng)作則由client類進(jìn)行。

public class ParamterCommand implements Command {
  private TV tv;
  private long time; //燈開多久,參數(shù)通過(guò)構(gòu)造器注入
  public MultiCommand(TV tv, long time){
      this.tv = tv;
  }

  @Override
  public void execute() {
      tv.on(this.time);
  }

  @Override
  public void undo() {
       //....
  }
}
  • 如果命令需要有返回值呢,畢竟head first書中的點(diǎn)餐例子中,客人下了單了,服務(wù)員通知廚師了,廚師也按照菜單做菜了,那做完了總得給客人吃吧? 有返回值就必須在execute方法上返回了,如果返回值能抽象化(返回值都屬于同一種類型),直接把void execute();改為AbstractResult execute();即可。不能抽象調(diào)用者和接受者之間就存在比較大的耦合了,調(diào)用者需要知道接收者的具體返回類型。----------------------其實(shí)命令模式更多的用處在于調(diào)用者只需要發(fā)出命令,調(diào)用者不知道具體執(zhí)行者什么時(shí)候怎么執(zhí)行,所以對(duì)于返回值也應(yīng)該是這樣的:廚師做好東西后繼續(xù)棄用另外一套命令模式,之前階段的命令模式傳的是菜單,當(dāng)前啟用的命令模式傳遞的是做好的一堆菜,甚至可以做好一盤上一盤。當(dāng)然也可以考慮使用觀察者模式來(lái)進(jìn)行后續(xù)上菜操作。

命令模式使用場(chǎng)景

  • 遙控器例子(經(jīng)典實(shí)現(xiàn))
  • 日志請(qǐng)求
  • 工作隊(duì)列
    and so on..........
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,345評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,494評(píng)論 3 416
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,283評(píng)論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,953評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,714評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,186評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,410評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,940評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,776評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,976評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,210評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評(píng)論 1 286
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,654評(píng)論 3 391
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,958評(píng)論 2 373

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

  • 1 場(chǎng)景問(wèn)題# 1.1 如何開機(jī)## 估計(jì)有些朋友看到這個(gè)標(biāo)題會(huì)非常奇怪,電腦裝配好了,如何開機(jī)?不就是按下啟動(dòng)按...
    七寸知架構(gòu)閱讀 2,854評(píng)論 1 59
  • 3.5 隊(duì)列請(qǐng)求## 所謂隊(duì)列請(qǐng)求,就是對(duì)命令對(duì)象進(jìn)行排隊(duì),組成工作隊(duì)列,然后依次取出命令對(duì)象來(lái)執(zhí)行。多用多線程或...
    七寸知架構(gòu)閱讀 2,033評(píng)論 4 54
  • 目錄 本文的結(jié)構(gòu)如下: 什么是命令模式 為什么要用該模式 模式的結(jié)構(gòu) 代碼示例 優(yōu)點(diǎn)和缺點(diǎn) 適用環(huán)境 模式應(yīng)用 總...
    w1992wishes閱讀 1,131評(píng)論 2 9
  • 意圖把方法調(diào)用封裝起來(lái)或?qū)⒁粋€(gè)請(qǐng)求封裝成一個(gè)對(duì)象從而實(shí)現(xiàn)“對(duì)象請(qǐng)求者”和“對(duì)象執(zhí)行者”的解耦。對(duì)請(qǐng)求進(jìn)行排隊(duì)、記錄...
    _chubby閱讀 279評(píng)論 0 0
  • “你怎么想起問(wèn)這,哎,發(fā)什么愣?”他拿筷子敲了一下我的頭。 “我中獎(jiǎng)了。”我怔怔的看著他說(shuō)。 “做夢(mèng)呢。” “我中...
    林夕楓起閱讀 332評(píng)論 0 1