序
不知道是不是因?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