設(shè)計(jì)模式大總結(jié)(六):命令模式

前言

最近看了命令模式吮铭,覺得很有意思霹抛,平時(shí)大家應(yīng)該都有使用過熬词,所以寫一點(diǎn)總結(jié)體會分享給大家栅表。

正文

首先我們先不談什么是命令模式笋鄙,直接寫點(diǎn)東西:

實(shí)現(xiàn)一個(gè)電視遙控器的功能:
1、遙控器有兩個(gè)鍵:開機(jī)鍵和關(guān)機(jī)鍵怪瓶。
2萧落、電視接收對應(yīng)的命令信號,執(zhí)行對應(yīng)的操作洗贰。

ok找岖,首先我們知道命令是一個(gè)抽象的概念,所以我們先寫一個(gè)Command借口:

/**
 * Created by li.zhipeng on 2017/9/26.
 *      命令接口
 */
public interface Command {

    void onCommand();
}

因?yàn)檫b控器要綁定對應(yīng)的電視機(jī)敛滋,所以我們的遙控器按鈕構(gòu)造方法里或者是setter能夠設(shè)置綁定的電視機(jī)许布,這樣我們創(chuàng)建一個(gè)基類,我們的目的是為了在基類復(fù)雜一些基本信息绎晃,例如按鈕的位置蜜唾,顏色等公有特性:

/**
 *      遙控器按鈕的基類
 */
public abstract class ControllerButton {

    private TV tv;

    public ControllerButton(TV tv){
        this.tv = tv;
    }

}

下面定義開機(jī)鍵和關(guān)機(jī)鍵的功能:

/**
 * 開機(jī)功能
 */
public class OpenCommand implements Command {
    @Override
    public void onCommand() {
        System.out.println("打開電視...");
        tv.open();
    }
}

/**
 * 關(guān)機(jī)功能
 */
public class CloseCommand implements Command {
    @Override
    public void onCommand() {
        System.out.println("關(guān)閉電視...");
        tv.close();
    }
}

使用時(shí)的代碼:

TV tv = new TV();

Command openCommand = new OpenCommand(tv);
Command closeCommand = new CloseCommand(tv);

openCommand.onCommand();
closeCommand.onCommand();

功能的擴(kuò)展

剛才我們已經(jīng)完成了基本功能的開發(fā)杂曲,我們的類的定義還是比較嚴(yán)格的,基類和接口都只做自己相關(guān)的事情袁余,我們定義的每一個(gè)類都盡可能的少承擔(dān)起整個(gè)功能的責(zé)任擎勘,符合我們開發(fā)的基本原則。

例如颖榜,我們可以去掉Command接口棚饵,把onCommand移動到基類ControllerButton中,但是這樣就加重了基類的負(fù)擔(dān)掩完,違背了我們開發(fā)的基本準(zhǔn)則噪漾,也影響到了之后的功能更擴(kuò)展,所以不建議這樣做的藤为。

但是很快新需求來了:

在發(fā)送命令的時(shí)候怪与,需要同時(shí)發(fā)送一條指示燈閃閃閃的命令,這樣用戶知道自己的命令到底發(fā)沒發(fā)出去缅疟,有助于用戶體驗(yàn)分别。

現(xiàn)在只有兩個(gè)按鈕,我們只要在調(diào)用的時(shí)候存淫,添加上指示燈閃閃的命令就可以了:

// 紅燈閃閃命令
lampBulingCommand.onCommand();
openCommand.onCommand();

這個(gè)時(shí)候一定有人站出來了:

我現(xiàn)在就要把Command去掉耘斩,直接在基類ControllerButton里面實(shí)現(xiàn)onCommand方法,這樣不用修改調(diào)用方法桅咆,比你這種實(shí)現(xiàn)吊太多@ㄊ凇!岩饼!

不得不說這是一個(gè)好辦法荚虚,以現(xiàn)在這個(gè)需求來看修改基類是最簡單的,但是違背了我們之前的開發(fā)原則籍茧,但是為了這位朋友的任性版述,我們暫時(shí)不阻止他,于是基類的代碼發(fā)生了改變:

/**
 *      遙控器按鈕的基類
 */
public abstract class ControllerButton implements Command{

    protected TV tv;

    /**
     * 指示燈閃閃命令
     * */
    private LampBulingCommand lampBulingCommand;

    public ControllerButton(TV tv){
        this.tv = tv;
        this.lampBulingCommand = lampBulingCommand;
    }
    
    /**
     * 對onCommand進(jìn)行包裝
     * */
     public void sendCommand(){
        if (lampBulingCommand != null){
            lampBulingCommand.onCommand();
        }
        onCommand();
    }

}

// 調(diào)用處寞冯,請注意這里已經(jīng)無法Command渴析,因?yàn)閟endCommand定義在基類里,而不是在Command接口里
OpenCommand openCommand = new OpenCommand(tv);
openCommand.sendCommand();

我們看到了吮龄,為了實(shí)現(xiàn)產(chǎn)品的需求俭茧,他做了3處修改:

1、添加屬性漓帚,保存指示燈閃閃命令母债。
2、修改構(gòu)造方法胰默,實(shí)例化指示燈閃閃命令场斑。
3漓踢、在onCommand外部定義了一個(gè)中轉(zhuǎn)函數(shù),執(zhí)行指示燈閃閃命令漏隐。
4喧半、修改調(diào)用的代碼。
5青责、最關(guān)鍵:已經(jīng)無法通過Command類型創(chuàng)建方法挺据。

但是他仍然陶醉在自己的世界里,認(rèn)為這個(gè)方法屌爆了脖隶。

但是很遺憾扁耐,很快新需求又來了:

在新增一條記憶命令,我們需要統(tǒng)計(jì)開機(jī)的次數(shù)产阱,但是不統(tǒng)計(jì)關(guān)機(jī)命令的次數(shù)婉称。

雖然這個(gè)需求有點(diǎn)變態(tài)了,但是我們還得硬著頭皮繼續(xù)寫构蹬,這個(gè)時(shí)候剛才的那位朋友又站出來了王暗,分分鐘就要解決這個(gè)問題:

/**
 *      遙控器按鈕的基類
 */
public abstract class ControllerButton implements Command{

    protected TV tv;

    /**
     * 指示燈閃閃命令
     * */
    private LampBulingCommand lampBulingCommand;
    
    /**
    * 記憶命令
    */
    private MemoryCommand memoryCommand;

    public ControllerButton(TV tv){
        this.tv = tv;
        this.lampBulingCommand = lampBulingCommand;
        memoryCommand = new MemoryCommand(tv);
    }
    
    /**
     * 對onCommand進(jìn)行包裝
     * */
     public void sendCommand(){
        // 指示燈閃閃
        lampBulingCommand.onCommand();
        // 如果是開機(jī)功能,要發(fā)送記憶命令
        if (this instanceof OpenCommand) {
            memoryCommand.onCommand();
        }
        onCommand();
    }

}

經(jīng)過修改后的代碼庄敛,雖然運(yùn)行正常俗壹,他自己已經(jīng)感覺到自己挖的坑越來越深,而其他人也出現(xiàn)了懷疑的態(tài)度藻烤,因?yàn)椋?/p>

1绷雏、父類已經(jīng)開始影響到子類的業(yè)務(wù)邏輯。

2怖亭、基類越來越臃腫:每一次內(nèi)部的屬性的增加和sendCommand方法的復(fù)雜度的上升涎显,都讓基類的變得越來越臃腫,并且基類已經(jīng)開始越權(quán)處理onCommand的邏輯兴猩,Command接口已經(jīng)形同虛設(shè)棺禾,類的閱讀和維護(hù)都開始出現(xiàn)了問題。

3峭跳、已經(jīng)無法通過Command創(chuàng)建命令實(shí)例,全部要被替換成ControllerButton缺前,類的語義出現(xiàn)了嚴(yán)重的危機(jī)蛀醉。

命令模式登場

經(jīng)過之前的討論,命令模式終于登場了衅码,于是咔咔咔重構(gòu)了代碼:

/**
 * 命令的執(zhí)行者拯刁,處于命令發(fā)起者和接收者之間,在這個(gè)過程中進(jìn)行處理
 */
public class Switch {

    private LampBulingCommand lampBulingCommand;

    private MemoryCommand memoryCommand;

    public Switch(TV tv) {
        lampBulingCommand = new LampBulingCommand(tv);
        memoryCommand = new MemoryCommand(tv);
    }

    public void excuteCommand(Command command) {
        // 指示燈閃閃
        lampBulingCommand.onCommand();
        // 如果是開機(jī)功能逝段,要發(fā)送記憶命令
        if (command instanceof OpenCommand) {
            memoryCommand.onCommand();
        }
        // 執(zhí)行參數(shù)命令
        command.onCommand();
    }

}

修改調(diào)用的代碼:

TV tv = new TV();
Command openCommand = new OpenCommand(tv);
Command closeCommand = new CloseCommand(tv);
//openCommand.onCommand();
//closeCommand.onCommand();
Switch s = new Switch(tv);
s.excuteCommand(openCommand);
s.excuteCommand(closeCommand);

是不是完全被驚艷到了垛玻,就是簡單~

現(xiàn)在開始進(jìn)入正題:什么是命令模式割捅?

命令模式是把一個(gè)操作和他的參數(shù),包裝成一個(gè)新的對象去執(zhí)行帚桩。
命令模式有四個(gè)部分:命令亿驾,接收者,執(zhí)行者账嚎,發(fā)起者莫瞬。

以剛才的demo為例,Command代表命令郭蕉,接收者是TV疼邀,執(zhí)行者是Switch,發(fā)起者也就是客戶端召锈。

從demo中看到旁振,隨著需求的變化,我們的每一次修改都要修改多個(gè)類涨岁,并且代碼的成本也很高拐袜,于是通過Switch把Command的執(zhí)行過程包裝了起來,也就是在發(fā)起者和接收者之間卵惦,隨意我們就可以根據(jù)需求阻肿,定制執(zhí)行的過程。

也可以理解成沮尿,Switch把基類中有關(guān)于Command的功能全部抽取了出來丛塌,作為一個(gè)獨(dú)立模塊。

經(jīng)過命令模式的重構(gòu)畜疾,我們之后的擴(kuò)展和修改赴邻,只要不改變open和close的核心功能,只要修改Switch類就可以了啡捶,這就是命令模式的優(yōu)點(diǎn)姥敛。

總結(jié)

最后我們對命令模式進(jìn)行一下總結(jié):

1、命令模式是對某一個(gè)操作的和其參數(shù)的封裝瞎暑,目的是維護(hù)這個(gè)操作的過程彤敛。

2、命令模式位于發(fā)起者和接收者之間了赌,對兩者進(jìn)行解耦墨榄,便于維護(hù)。

3勿她、命令模式能夠幫助我們明確類和接口的定義的目的袄秩,理解面向?qū)ο缶幊獭?/p>

順便強(qiáng)調(diào)一下,過多的if-else是糟糕的代碼,凸顯出程序的笨重之剧,例如demo中郭卫,我們可以利用開關(guān)來解決:

/**
*  在基類中增加boolean型開關(guān),并增加參數(shù)為ControllerButton的方法
*/
public class Switch {

    ...

    public void excuteCommand(ControllerButton controllerButton) {
        // 如果是開機(jī)功能背稼,要發(fā)送記憶命令
        if (controllerButton.isNeedMemory()) {
            memoryCommand.onCommand();
        }
        excuteCommand(controllerButton);
    }

    public void excuteCommand(Command command) {
        // 指示燈閃閃
        lampBulingCommand.onCommand();
        // 執(zhí)行參數(shù)命令
        command.onCommand();
    }

}

最后要說的是贰军,不要隨意違背開發(fā)的基本原則,例如上面的那位朋友雇庙,這些規(guī)則是前輩經(jīng)過長時(shí)間的研究總結(jié)的結(jié)晶谓形,當(dāng)然這些經(jīng)驗(yàn)不一定是對的,也不適于所有的場景疆前,但是如果你非要這么做寒跳,請做好充足的準(zhǔn)備。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末竹椒,一起剝皮案震驚了整個(gè)濱河市童太,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌胸完,老刑警劉巖书释,帶你破解...
    沈念sama閱讀 211,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異赊窥,居然都是意外死亡爆惧,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,347評論 3 385
  • 文/潘曉璐 我一進(jìn)店門锨能,熙熙樓的掌柜王于貴愁眉苦臉地迎上來扯再,“玉大人,你說我怎么就攤上這事址遇∠ㄗ瑁” “怎么了?”我有些...
    開封第一講書人閱讀 157,435評論 0 348
  • 文/不壞的土叔 我叫張陵倔约,是天一觀的道長秃殉。 經(jīng)常有香客問我,道長浸剩,這世上最難降的妖魔是什么钾军? 我笑而不...
    開封第一講書人閱讀 56,509評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮绢要,結(jié)果婚禮上巧颈,老公的妹妹穿的比我還像新娘。我一直安慰自己袖扛,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,611評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蛆封,像睡著了一般唇礁。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上惨篱,一...
    開封第一講書人閱讀 49,837評論 1 290
  • 那天盏筐,我揣著相機(jī)與錄音,去河邊找鬼砸讳。 笑死琢融,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的簿寂。 我是一名探鬼主播漾抬,決...
    沈念sama閱讀 38,987評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼常遂!你這毒婦竟也來了纳令?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,730評論 0 267
  • 序言:老撾萬榮一對情侶失蹤克胳,失蹤者是張志新(化名)和其女友劉穎平绩,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體漠另,經(jīng)...
    沈念sama閱讀 44,194評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡捏雌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,525評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了笆搓。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片性湿。...
    茶點(diǎn)故事閱讀 38,664評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖砚作,靈堂內(nèi)的尸體忽然破棺而出窘奏,到底是詐尸還是另有隱情,我是刑警寧澤葫录,帶...
    沈念sama閱讀 34,334評論 4 330
  • 正文 年R本政府宣布着裹,位于F島的核電站,受9級特大地震影響米同,放射性物質(zhì)發(fā)生泄漏骇扇。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,944評論 3 313
  • 文/蒙蒙 一面粮、第九天 我趴在偏房一處隱蔽的房頂上張望少孝。 院中可真熱鬧,春花似錦熬苍、人聲如沸稍走。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,764評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽婿脸。三九已至粱胜,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間狐树,已是汗流浹背焙压。 一陣腳步聲響...
    開封第一講書人閱讀 31,997評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留抑钟,地道東北人涯曲。 一個(gè)月前我還...
    沈念sama閱讀 46,389評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像在塔,于是被迫代替她去往敵國和親幻件。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,554評論 2 349

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理心俗,服務(wù)發(fā)現(xiàn)傲武,斷路器,智...
    卡卡羅2017閱讀 134,633評論 18 139
  • 1 場景問題# 1.1 如何開機(jī)## 估計(jì)有些朋友看到這個(gè)標(biāo)題會非常奇怪城榛,電腦裝配好了揪利,如何開機(jī)?不就是按下啟動按...
    七寸知架構(gòu)閱讀 2,819評論 1 59
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,790評論 25 707
  • 1.下載cuda sdk,并安裝 https://developer.nvidia.com/cuda-downlo...
    G風(fēng)閱讀 1,185評論 0 0
  • “終身所約,永結(jié)為好,琴瑟再御,歲月靜好” “即使身敗名裂狠持,受萬人責(zé)辱疟位,我也愿為你冒這個(gè)天下大不為……” 幼時(shí)同你...
    憫欣兒閱讀 226評論 0 0