淺談設(shè)計模式7——命令模式


不知道是不是因?yàn)榭磿鵂顟B(tài)不好羔飞,書上的例子確實(shí)總是有種云里霧里的感覺宵统,不過幸好有程序,看程序基本上就明白這個所謂的命令模式大概是怎么回事了借跪。

1、背景
為了簡化說明愧薛,書中的例子將在此處簡化。現(xiàn)在個遙控器衫画,共有14個按鈕毫炉,左邊7個表示“開”,右邊7個表示“關(guān)”削罩。這樣瞄勾,每一對開關(guān)可以對應(yīng)一種家用電器。當(dāng)按下開的時候弥激,對應(yīng)的家用電器啟動进陡。當(dāng)按下關(guān)的時候,家用電器關(guān)閉微服。
如果到這來說趾疚,其實(shí)就很簡單∫栽蹋可以將每對按鈕設(shè)定為一個類糙麦,類中存有開啟和關(guān)閉某個家用電器的操作。用一個集合對象來存儲這7組按鈕即可丛肮。但是赡磅,問題來了。如果原來風(fēng)扇扔掉了宝与,換成了空調(diào)——也就是控制的家用電器的種類發(fā)生了變化焚廊,或者說有一些家電不用了,或者更極端些习劫,希望一組按鈕能開啟咆瘟、關(guān)閉所有家電——總之,遙控器的功能要發(fā)生改變诽里,怎么辦搞疗?

首先確認(rèn)的是,為了保證更好的封裝特性须肆,不能修改遙控器類匿乃。那么據(jù)此,我們引入當(dāng)前的模式:命令模式豌汇。

2幢炸、命令模式
命令模式將:發(fā)出命令的控制實(shí)體(遙控器上的按鈕),接受命令的實(shí)體(遙控器上發(fā)出的不同的紅外線光)拒贱,執(zhí)行命令的功能實(shí)體(家用電器)三者進(jìn)行剝離宛徊。
接受命令的實(shí)體相當(dāng)于遙控器與家電的一個通用接口佛嬉,或者說是一個管道。管道一頭連接著發(fā)出命令的實(shí)體闸天,為控制實(shí)體提供一個統(tǒng)一的發(fā)出命令的接口暖呕。管道另一頭連接著執(zhí)行命令的功能實(shí)體,為功能實(shí)體提供一個統(tǒng)一接收命令的接口苞氮。命令在這個管道上流通湾揽。

好吧,這么說還是很抽象笼吟,我覺得上程序應(yīng)該更能有所體會库物。

public class Test2 {
    public static void main(String argv[])
    {
        Light light=new Light();//定義電燈
        Lighton lighton=new Lighton(light);//定義關(guān)于電燈開操作的接收實(shí)體
        Lightoff lightoff=new Lightoff(light);//定義關(guān)于電燈關(guān)操作的接收實(shí)體
        
        CeilingFan ceilingFan=new CeilingFan();
        Ceilingfanon ceilingfanon=new Ceilingfanon(ceilingFan);
        Ceilingfanoff ceilingfanoff=new Ceilingfanoff(ceilingFan);
        
        CtlCommand con=new CtlCommand(2); //定義控制實(shí)體
        con.setExecutors(0, lighton, lightoff); //將接收實(shí)體加入控制實(shí)體中
        con.setExecutors(1, ceilingfanon, ceilingfanoff);
        
        con.pushOnButton(0);//控制實(shí)體,通過接受實(shí)體贷帮,調(diào)用執(zhí)行實(shí)體的功能
        con.pushOnButton(1);
        
        con.pushOffButton(0);
        con.pushOffButton(1);
    }
    
}

//發(fā)出命令的控制實(shí)體
class CtlCommand
{
    private Executor[] onExecutors;
    private Executor[] offExecutors;
    
    public CtlCommand(int buttonAmount) //給出按鈕
    {
        this.onExecutors=new Executor[buttonAmount]; 
        this.offExecutors=new Executor[buttonAmount];
        
        for (int i = 0; i < offExecutors.length; i++) {
            this.onExecutors[i]=new Noexecute();//Noexecute為不會產(chǎn)生任何功能的功能實(shí)體戚揭。可以看作是虛擬的家用電器撵枢。
            this.offExecutors[i]=new Noexecute();
        }
    }
    
    public void setExecutors(int buttonNumber, Executor onExecutor, Executor offExecutor) {//給每個按鈕配置接收實(shí)體
        this.onExecutors[buttonNumber]=onExecutor;
        this.offExecutors[buttonNumber]=offExecutor;
    }
    
    public void pushOnButton(int buttonNumber){//接收實(shí)體提供統(tǒng)一的execute()接口民晒,調(diào)用功能實(shí)體的功能
        this.onExecutors[buttonNumber].execute();
    }
    public void pushOffButton(int buttonNumber){
        this.offExecutors[buttonNumber].execute();
    }
}



//定義了2個家用電器
class Light//燈
{
    public void on(){}
    public void off(){}
}



class CeilingFan//吊扇
{
    //風(fēng)速
    public void low(){}
    public void medium(){}
    public void high(){}
    
    //開關(guān)
    public void on(){}
    public void off(){}
}

//定義接受命令實(shí)體的統(tǒng)一接口
interface Executor
{
    public void execute();
}

//電燈接收實(shí)體
class Lighton implements Executor
{
    Light light;
    public Lighton(Light light) {this.light=light;}
    public void execute() {this.light.on();}
}

class Lightoff implements Executor
{
    Light  light;
    public Lightoff(Light light) {this.light=light;}
    @Override
    public void execute() {this.light.off();}
}

//電扇接受實(shí)體
class Ceilingfanon implements Executor
{
    CeilingFan ceilingFan;
    public Ceilingfanon(CeilingFan ceilingFan) {
        this.ceilingFan=ceilingFan;
    }
    @Override
    public void execute() {
        this.ceilingFan.on();
        this.ceilingFan.medium();       
    }
}

class Ceilingfanoff implements Executor
{
    CeilingFan ceilingFan;
    public Ceilingfanoff(CeilingFan ceilingFan) {
        this.ceilingFan=ceilingFan;
    }
    @Override
    public void execute() {
        this.ceilingFan.off();
    }
}
//空接收命令實(shí)體
class Noexecute implements Executor
{
    @Override
    public void execute() {}
}

不得不說,這個模式確實(shí)看起來很復(fù)雜锄禽,一方面涉及到的類和接口比較多镀虐。另一方面,每個類或者接口之間的關(guān)系也比較復(fù)雜沟绪。但是復(fù)雜并不代表一點(diǎn)規(guī)律都沒有刮便。下面將詳細(xì)說明這些關(guān)系,以及其中蘊(yùn)含的規(guī)律:

3绽慈、代碼解析
(1)程序共有兩個功能實(shí)體:Light和CeilingFan(此處簡稱燈和吊扇)恨旱。其中燈實(shí)體只有on()和off()方法,因此恰可以對應(yīng)遙控器按鈕的buttonOn和buttonOff坝疼。而吊扇實(shí)體中搜贤,由于還可以調(diào)節(jié)風(fēng)速,因此規(guī)定钝凶,對于吊扇的buttonOn來說仪芒,不僅要把吊扇打開,還需要指定風(fēng)速為medium()耕陷。吊扇的buttonOff則對應(yīng)off()函數(shù)掂名。

(2)由于一個按鈕對應(yīng)一個(或一組)操作,因此需要將每個操作哟沫,再次封裝到一個類中饺蔑。這個類就是接收實(shí)體。其使用了統(tǒng)一的Executor接口嗜诀。真實(shí)由于這個原因猾警,所以出現(xiàn)了LightOn類和LightOff類孔祸,CeilingFanon類和CeilingFanoff類。這四個類都實(shí)現(xiàn)了execute()方法发皿,從而為控制實(shí)體提供了統(tǒng)一的調(diào)用接口崔慧。而具體的操作,則通過execute()方法進(jìn)行封裝穴墅。
同時惶室,還需注意到,這些接收實(shí)體以組合方式封救,將功能實(shí)體包含進(jìn)來。這也是第一步的解耦過程捣作。因?yàn)橥活愋偷牡跎扔幔赡苡钟泻芏鄠€品牌。采用組合的方式券躁,極大地放寬了吊扇的品種惩坑。

(3)而控制實(shí)體相當(dāng)于一個大容器。他將所有的Executor接口對應(yīng)的類型都聚集到這里也拜,然后再用Executor提供的統(tǒng)一方法以舒,對某個函數(shù)進(jìn)行調(diào)用。這是第二步的解耦慢哈÷樱控制實(shí)體只是判斷方法——控制實(shí)體是方法的函數(shù)(此處用到了數(shù)學(xué)概念,數(shù)學(xué)思維確實(shí)真的很好用卵贱,尤其是分析問題的時候)滥沫,而不是方法對應(yīng)的某個對象的函數(shù)(即控制實(shí)體是execute()的函數(shù),而不是execute()所屬對象的函數(shù))键俱。

這樣兰绣,便將控制實(shí)體、接受實(shí)體编振、功能實(shí)體進(jìn)行了剝離缀辩。而每個實(shí)體,又有一點(diǎn)點(diǎn)聯(lián)系——即他們的聯(lián)系全都集中在execute()這個方法中踪央。

功能實(shí)體為接受實(shí)體提供了execute()的內(nèi)容臀玄。而接收實(shí)體為控制實(shí)體提供了execute()這張皮〕澹——突然想到了月餅:
功能實(shí)體就好像餡料镐牺,接收實(shí)體就好像月餅皮,控制實(shí)體就好像食客魁莉。

4睬涧、繼續(xù)工作
其實(shí)這個事還是沒有說完募胃,書上又進(jìn)一步做了更深入的討論:
1)遙控器上又提供了一個undo()撤回按鈕。當(dāng)按這個按鈕的時候畦浓,上一個操作將被撤回痹束。比如上一個操作為:開燈。那按一下undo()按鈕讶请,就能實(shí)現(xiàn)關(guān)燈的操作祷嘶。
這個其實(shí)也很好做。還是看代碼:

public class Test2 {
    public static void main(String argv[])
    {
        Light light=new Light();//定義電燈
        Lighton lighton=new Lighton(light);//定義關(guān)于電燈開操作的接收實(shí)體
        Lightoff lightoff=new Lightoff(light);//定義關(guān)于電燈關(guān)操作的接收實(shí)體
        
        CeilingFan ceilingFan=new CeilingFan();
        Ceilingfanon ceilingfanon=new Ceilingfanon(ceilingFan);
        Ceilingfanoff ceilingfanoff=new Ceilingfanoff(ceilingFan);
        
        CtlCommand con=new CtlCommand(2); //定義控制實(shí)體
        con.setExecutors(0, lighton, lightoff); //將接收實(shí)體加入控制實(shí)體中
        con.setExecutors(1, ceilingfanon, ceilingfanoff);
        
        con.pushOnButton(0);//控制實(shí)體夺溢,通過接受實(shí)體论巍,調(diào)用執(zhí)行實(shí)體的功能
        con.pushOnButton(1);
        
        con.pushOffButton(0);
        con.pushOffButton(1);
    }
    
}

//發(fā)出命令的控制實(shí)體
class CtlCommand
{
    private Executor[] onExecutors;
    private Executor[] offExecutors;
    private Executor undoExecutor;//增加一個記錄上次執(zhí)行操作的屬性
    
    public CtlCommand(int buttonAmount) //給出按鈕
    {
        this.onExecutors=new Executor[buttonAmount]; 
        this.offExecutors=new Executor[buttonAmount];
        
        this.undoExecutor=new Noexecute();//初始化一個undo
        
        for (int i = 0; i < offExecutors.length; i++) {
            this.onExecutors[i]=new Noexecute();//Noexecute為不會產(chǎn)生任何功能的功能實(shí)體》缦欤可以看作是虛擬的家用電器嘉汰。
            this.offExecutors[i]=new Noexecute();
        }
    }
    
    public void setExecutors(int buttonNumber, Executor onExecutor, Executor offExecutor) {//給每個按鈕配置接收實(shí)體
        this.onExecutors[buttonNumber]=onExecutor;
        this.offExecutors[buttonNumber]=offExecutor;
    }
    
    public void pushOnButton(int buttonNumber){//接收實(shí)體提供統(tǒng)一的execute()接口,調(diào)用功能實(shí)體的功能
        this.onExecutors[buttonNumber].execute();
        this.undoExecutor=this.onExecutors[buttonNumber];
    }
    public void pushOffButton(int buttonNumber){
        this.offExecutors[buttonNumber].execute();
        this.undoExecutor=this.offExecutors[buttonNumber];
    }
    
    public void pushUndoButton()//點(diǎn)擊撤銷按鈕
    {
        this.undoExecutor.undo();
    }
    
}



//定義了2個家用電器
class Light//燈
{
    public void on(){}
    public void off(){}
}



class CeilingFan//吊扇
{
    //風(fēng)速
    public void low(){}
    public void medium(){}
    public void high(){}
    
    //開關(guān)
    public void on(){}
    public void off(){}
}

//定義接受命令實(shí)體的統(tǒng)一接口
interface Executor
{
    public void execute();
    public void undo();//提供撤銷操作接口
}

//電燈接收實(shí)體
class Lighton implements Executor
{
    Light light;
    public Lighton(Light light) {this.light=light;}
    public void execute() {this.light.on();}
    public void undo(){this.light.off();}
}

class Lightoff implements Executor
{
    Light  light;
    public Lightoff(Light light) {this.light=light;}
    @Override
    public void execute() {this.light.off();}
    public void undo(){this.light.on();}
}

//電扇接受實(shí)體
class Ceilingfanon implements Executor
{
    CeilingFan ceilingFan;
    public Ceilingfanon(CeilingFan ceilingFan) {
        this.ceilingFan=ceilingFan;
    }
    @Override
    public void execute() {
        this.ceilingFan.on();
        this.ceilingFan.medium();       
    }
    public void undo(){this.ceilingFan.off();}
}

class Ceilingfanoff implements Executor
{
    CeilingFan ceilingFan;
    public Ceilingfanoff(CeilingFan ceilingFan) {
        this.ceilingFan=ceilingFan;
    }
    @Override
    public void execute() {
        this.ceilingFan.off();
    }
    public void undo(){this.ceilingFan.off();}
}
//空接收命令實(shí)體
class Noexecute implements Executor
{
    @Override
    public void execute() {}
    public void undo(){}
}

這個就不詳細(xì)解析了状勤。大家注意下四個位置(兩個實(shí)體鞋怀,一個接口)。
第一個位置:接口Executor中增加了undo接口持搜。
第二個位置:所以密似,實(shí)現(xiàn)該接口的所有類,都給出了相對于execute()相反操作的undo()方法葫盼。
第三個位置:控制實(shí)體增加了一個屬性残腌,該屬性記錄上次操作對應(yīng)的接收實(shí)體。
第四個位置:增加了按鈕對應(yīng)的方法pushUndoButton()贫导。

2)對于像吊扇這種有風(fēng)速控制的家電废累,是否可以根據(jù)其前一個狀態(tài)恢復(fù)?
3)是否一個按鈕可以控制一堆家用電器脱盲,比如同時開啟電扇邑滨、電燈、電視這三個家用電器钱反?
4)是否可以記錄最后若干個操作掖看,當(dāng)多次按undo()按鈕時,可以順次進(jìn)行操作的回退操作面哥。比如操作序列:開電視哎壳、開燈、開電扇尚卫、開洗衣機(jī)归榕。則回退操作序列為:關(guān)洗衣機(jī)、關(guān)電扇吱涉、關(guān)燈刹泄、關(guān)電視外里?

此處就不仔細(xì)說了。架構(gòu)已經(jīng)給出特石。:-P

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末盅蝗,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子姆蘸,更是在濱河造成了極大的恐慌墩莫,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件逞敷,死亡現(xiàn)場離奇詭異狂秦,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)推捐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進(jìn)店門裂问,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人玖姑,你說我怎么就攤上這事愕秫】猓” “怎么了焰络?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長符喝。 經(jīng)常有香客問我闪彼,道長,這世上最難降的妖魔是什么协饲? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任畏腕,我火速辦了婚禮,結(jié)果婚禮上茉稠,老公的妹妹穿的比我還像新娘描馅。我一直安慰自己,他們只是感情好而线,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布铭污。 她就那樣靜靜地躺著,像睡著了一般膀篮。 火紅的嫁衣襯著肌膚如雪嘹狞。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天誓竿,我揣著相機(jī)與錄音磅网,去河邊找鬼。 笑死筷屡,一個胖子當(dāng)著我的面吹牛涧偷,可吹牛的內(nèi)容都是我干的簸喂。 我是一名探鬼主播,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼嫂丙,長吁一口氣:“原來是場噩夢啊……” “哼娘赴!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起跟啤,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤诽表,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后隅肥,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體竿奏,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年腥放,在試婚紗的時候發(fā)現(xiàn)自己被綠了泛啸。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡秃症,死狀恐怖候址,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情种柑,我是刑警寧澤岗仑,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站聚请,受9級特大地震影響荠雕,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜驶赏,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一炸卑、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧煤傍,春花似錦盖文、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蒋失,卻和暖如春返帕,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背篙挽。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工荆萤, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓链韭,卻偏偏與公主長得像偏竟,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子敞峭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評論 2 359

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