設(shè)計(jì)模式--命令模式

一、問題的產(chǎn)生
如果我們想設(shè)計(jì)一個(gè)遙控器


image.png

而需要遙控的電器,各自都有不同的類


image.png

如果把遙控器和操作電器的類捆綁在一起,如果新增電器或已有電器新增方法的話,都會(huì)改動(dòng)現(xiàn)有的設(shè)計(jì),明顯不是一個(gè)好的設(shè)計(jì),所以需要把“動(dòng)作的請(qǐng)求者”(遙控器)和“動(dòng)作的執(zhí)行者”(電器)進(jìn)行解耦。例如可以把請(qǐng)求(打開電燈)封裝成一個(gè)特定對(duì)象(客廳電燈對(duì)象),當(dāng)遙控器按鈕被按下時(shí),請(qǐng)客廳電燈去做相關(guān)的工作,而遙控器是不需要知道工作內(nèi)容是什么的,這樣遙控器就和電器解耦了。

二、簡化問題
餐廳是如何工作的呢?


image.png

細(xì)化一下


image.png

這樣可以看出餐廳的工作也是一個(gè)命令模式的例子,不同的客戶創(chuàng)建不同的訂單createOrder(),女招待拿到訂單takeOrder(),把它交給廚師,也就是給廚師下了個(gè)命令orderUp(),要做菜了。所以女招待和廚師之間是解耦的,女招待的訂單封裝了餐點(diǎn)的細(xì)節(jié),她只要調(diào)用每個(gè)訂單的方法即可,而廚師看了訂單就知道該做什么菜,廚師和女招待之間不需要直接溝通。
把上面的餐廳流程圖改成命令模式
image.png

把餐廳的工作過程用代碼表現(xiàn)一下吧
1、實(shí)現(xiàn)命令接口
首先,讓所有的命令對(duì)象實(shí)現(xiàn)相同的包含一個(gè)方法的接口,就是orderUp()(讓廚師開始做菜),一般慣用的名稱為execute()

public interface Command {
  public void execute();
}

2、實(shí)現(xiàn)做菜的命令

//這是一個(gè)命令,所以需要實(shí)現(xiàn)Command接口
public class CookCommand implements Command {
  Beef beef;
  //構(gòu)造器被傳入了牛肉,以便讓這個(gè)命令控制,然后記錄在實(shí)例變量中
  //一旦調(diào)用execute(),就有這個(gè)beef稱為接受者,負(fù)責(zé)接受請(qǐng)求
  public CookCommand (Beef beef) {
    this.beef = beef;
  }
  //這個(gè)execute()方法調(diào)用接收對(duì)象(beef)的cook()方法
  public void execute() {
    beef.cook();
  }
}

3、使用命令對(duì)象

//女招待持有這個(gè)命令,這個(gè)命令控制著廚師
public class Waitress {
  Command chef;
  //用于設(shè)置女招待控制的命令
  public void setCommand(Command command) {
    chef = command;
  }
  //當(dāng)女招待把訂單放到廚師窗口時(shí),就調(diào)用這個(gè)方法
  public void startCook() {
    chef.execute();
  }
}

4、測試一下

//命令模式的客戶
public class Custom {
  public static void main(String[] args) {
    //女招待就是調(diào)用者,會(huì)傳入一個(gè)命令對(duì)象,用于發(fā)出請(qǐng)求
    Waitress waitress = new Waitress();
    //創(chuàng)建了一塊牛肉,也就是請(qǐng)求的接受者
    Beef beef = new Beef();
    //創(chuàng)建一個(gè)命令,將接收者傳給它
    CookCommand cook = new CookCommand(beef);
    //把命令傳給調(diào)用者
    waitress.setCommand(cook);
    //請(qǐng)求做菜
    waitress.startCook();
  }
}

三、命令模式
1、定義
命令模式將“請(qǐng)求”封裝成對(duì)象,以便使用不同的請(qǐng)求、隊(duì)列或日志來參數(shù)化其他對(duì)象,命令模式也支持可撤銷的操作。
也就是說一個(gè)命令對(duì)象通過在特定接受者上綁定一組動(dòng)作來封裝一個(gè)請(qǐng)求,要達(dá)到這一點(diǎn),命令對(duì)象必須將動(dòng)作和接收者都包進(jìn)對(duì)象中,而對(duì)象只暴露出一個(gè)execute()方法,當(dāng)此方法被調(diào)用時(shí),接收者就會(huì)進(jìn)行這些動(dòng)作。從外部看,其他對(duì)象不知道是哪個(gè)接收者進(jìn)行了什么動(dòng)作,只知道如果調(diào)用了execute()方法,就可以發(fā)送請(qǐng)求。
2、類圖


image.png

四、完成遙控器的設(shè)計(jì)
將遙控器的每個(gè)插槽,對(duì)應(yīng)一個(gè)命令,這樣遙控器就成為調(diào)用者,按下按鈕是,相應(yīng)命令的對(duì)象的execute()方法就會(huì)被調(diào)用,也就是接收者(電燈,電扇,音響等)的動(dòng)作被調(diào)用。


image.png

1、實(shí)現(xiàn)遙控器
public class RemoteControl {
  //要處理多個(gè)電器的命令,使用數(shù)組進(jìn)行記錄
  Command[] onCommands;
  //初始化開數(shù)組
  public RemoteControl() {
    onCommands = new Command[7];
    Command noCommand = new NoCommand();
    for(int i = 0; i < 7; i++) {
        onCommands[i] = noCommand();
    }
  }
  //命令設(shè)置需要3個(gè)參數(shù)--按鈕位置,開命令
  public void setCommand(int slot, Command onCommand) {
    onCommands[slot] = onCommand;
  }
  //當(dāng)按下開按鈕時(shí),就會(huì)調(diào)用相應(yīng)的方法
  public void onButtonWasPushed(int slot) {
    onCommands[slot].execute();
  }
}

2、實(shí)現(xiàn)命令(打開音響)

pubic class StereoOnWithCDCommand implements Command {
  Stereo stereo;
  public StereoOnWithCDCommand (Stereo stereo) {
    this.stereo= stereo;
  }
  public void execute() {
    stereo.on();
    stereo.setCD();
    stereo.setVolume(11);
  }
}

3、測試一下

public class RemoteLoader {
  public static void main(String[] args) {
    RemoteControl remoteControl = new RemoteControl();
    //創(chuàng)建裝置在合適的位置上
    Light livingRoomLight = new Light(“Living Room”);
    Stereo stereo = new Stereo(“Living Room”);
    ...
    //創(chuàng)建音響的命令
    StereoOnWithCDCommand stereoOnWithCD = new StereoOnWithCDCommand(stereo);
    ...
    //把命令加載到遙控器中
    remoteControl.setCommand(0, stereoOnWithCD);
    ...
    //按下按鈕
    remoteControl.onButtonWasPushed(0);
    ...
  }
}

遙控器的類圖


image.png

五、命令模式的其他用途
1、撤銷操作
使用一個(gè)堆棧記錄操作過程的每一個(gè)命令,當(dāng)按下撤銷按鈕時(shí),從堆棧中取出最上層的命令,調(diào)用他的撤銷方法。
2、隊(duì)列請(qǐng)求(日常安排,線程池,工作隊(duì)列)
一個(gè)工作隊(duì)列,在一端添加命令,另一端是線程,線程從隊(duì)列中取出一個(gè)命令,調(diào)用她的execute()方法,等這個(gè)調(diào)用完成后,將此命令丟棄,再取出下一個(gè)命令。工作隊(duì)列類和進(jìn)行計(jì)算的對(duì)象之間是完全解耦的,此刻線程可能在進(jìn)行財(cái)務(wù)運(yùn)算,下一刻卻在讀取網(wǎng)絡(luò)數(shù)據(jù),工作隊(duì)列對(duì)象不關(guān)心在做什么,只是取出命令對(duì)象,然后調(diào)用它的execute()方法。Android中的Runnable,Thread就是命令模式
3、日志請(qǐng)求
一些應(yīng)用需要將所有的動(dòng)作都記錄在日志中,并能在系統(tǒng)死機(jī)后,重新調(diào)用這些動(dòng)作恢復(fù)到之前的狀態(tài),新增store(),load()方法,命令模式就能支持了。當(dāng)執(zhí)行命令時(shí),將歷史記錄存儲(chǔ)在磁盤中,一旦系統(tǒng)死機(jī),就可以將命令對(duì)象重新加載,并成批的調(diào)用這些對(duì)象的execute()方法。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 目錄 本文的結(jié)構(gòu)如下: 什么是命令模式 為什么要用該模式 模式的結(jié)構(gòu) 代碼示例 優(yōu)點(diǎn)和缺點(diǎn) 適用環(huán)境 模式應(yīng)用 總...
    w1992wishes閱讀 1,236評(píng)論 2 9
  • 意圖 簡單的說,命令模式可將“動(dòng)作的請(qǐng)求者”從“動(dòng)作的執(zhí)行者”對(duì)象中解耦。 將一個(gè)請(qǐng)求封裝為一個(gè)對(duì)象,從而使你可用...
    tomas家的小撥浪鼓閱讀 13,435評(píng)論 4 5
  • Game Programming Patterns -- Command 原文地址:http://gameprog...
    Felicx閱讀 2,004評(píng)論 2 5
  • 一. 簡介 命令模式 ,將一個(gè)請(qǐng)求封裝為一個(gè)對(duì)象,從而使你可用不同的請(qǐng)求對(duì)客戶進(jìn)行參數(shù)化;對(duì)請(qǐng)求排隊(duì)或記錄請(qǐng)求日志...
    BrightLoong閱讀 367評(píng)論 0 1
  • 因?yàn)楹⒆雍退耐瑢W(xué)之間爭吵,對(duì)方家長找我質(zhì)問。我表明態(tài)度,孩子們之間的事情讓孩子們自已解決。我是不干涉的。當(dāng)我聽到...
    楚楚蒙閱讀 336評(píng)論 1 2

友情鏈接更多精彩內(nèi)容