前言
最近看了命令模式吮铭,覺得很有意思霹抛,平時(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)備。