設計模式大總結(六):命令模式
前言
最近看了命令模式,覺得很有意思,平時大家應該都有使用過,所以寫一點總結體會分享給大家。
正文
首先我們先不談什麼是命令模式,直接寫點東西:
實現一個電視遙控器的功能:
1、遙控器有兩個鍵:開機鍵和關機鍵。
2、電視接收對應的命令訊號,執行對應的操作。
ok,首先我們知道命令是一個抽象的概念,所以我們先寫一個Command藉口:
/**
* Created by li.zhipeng on 2017/9/26.
* 命令介面
*/
public interface Command {
void onCommand();
}
因為遙控器要繫結對應的電視機,所以我們的遙控器按鈕構造方法裡或者是setter能夠設定繫結的電視機,這樣我們建立一個基類,我們的目的是為了在基類複雜一些基本資訊,例如按鈕的位置,顏色等公有特性:
/**
* 遙控器按鈕的基類
*/
public abstract class ControllerButton {
private TV tv;
public ControllerButton(TV tv){
this.tv = tv;
}
}
下面定義開機鍵和關機鍵的功能:
/**
* 開機功能
*/
public class OpenCommand implements Command {
@Override
public void onCommand() {
System.out.println("開啟電視...");
tv.open();
}
}
/**
* 關機功能
*/
public class CloseCommand implements Command {
@Override
public void onCommand() {
System.out.println("關閉電視...");
tv.close();
}
}
使用時的程式碼:
TV tv = new TV();
Command openCommand = new OpenCommand(tv);
Command closeCommand = new CloseCommand(tv);
openCommand.onCommand();
closeCommand.onCommand();
功能的擴充套件
剛才我們已經完成了基本功能的開發,我們的類的定義還是比較嚴格的,基類和介面都只做自己相關的事情,我們定義的每一個類都儘可能的少承擔起整個功能的責任,符合我們開發的基本原則。
例如,我們可以去掉Command介面,把onCommand移動到基類ControllerButton中,但是這樣就加重了基類的負擔,違背了我們開發的基本準則,也影響到了之後的功能更擴充套件,所以不建議這樣做的。
但是很快新需求來了:
在傳送命令的時候,需要同時傳送一條指示燈閃閃閃的命令,這樣使用者知道自己的命令到底發沒發出去,有助於使用者體驗。
現在只有兩個按鈕,我們只要在呼叫的時候,新增上指示燈閃閃的命令就可以了:
// 紅燈閃閃命令
lampBulingCommand.onCommand();
openCommand.onCommand();
這個時候一定有人站出來了:
我現在就要把Command去掉,直接在基類ControllerButton裡面實現onCommand方法,這樣不用修改呼叫方法,比你這種實現吊太多!!!
不得不說這是一個好辦法,以現在這個需求來看修改基類是最簡單的,但是違背了我們之前的開發原則,但是為了這位朋友的任性,我們暫時不阻止他,於是基類的程式碼發生了改變:
/**
* 遙控器按鈕的基類
*/
public abstract class ControllerButton implements Command{
protected TV tv;
/**
* 指示燈閃閃命令
* */
private LampBulingCommand lampBulingCommand;
public ControllerButton(TV tv){
this.tv = tv;
this.lampBulingCommand = lampBulingCommand;
}
/**
* 對onCommand進行包裝
* */
public void sendCommand(){
if (lampBulingCommand != null){
lampBulingCommand.onCommand();
}
onCommand();
}
}
// 呼叫處,請注意這裡已經無法Command,因為sendCommand定義在基類裡,而不是在Command介面裡
OpenCommand openCommand = new OpenCommand(tv);
openCommand.sendCommand();
我們看到了,為了實現產品的需求,他做了3處修改:
1、新增屬性,儲存指示燈閃閃命令。
2、修改構造方法,例項化指示燈閃閃命令。
3、在onCommand外部定義了一箇中轉函式,執行指示燈閃閃命令。
4、修改呼叫的程式碼。
5、最關鍵:已經無法通過Command型別建立方法。
但是他仍然陶醉在自己的世界裡,認為這個方法屌爆了。
但是很遺憾,很快新需求又來了:
在新增一條記憶命令,我們需要統計開機的次數,但是不統計關機命令的次數。
雖然這個需求有點變態了,但是我們還得硬著頭皮繼續寫,這個時候剛才的那位朋友又站出來了,分分鐘就要解決這個問題:
/**
* 遙控器按鈕的基類
*/
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進行包裝
* */
public void sendCommand(){
// 指示燈閃閃
lampBulingCommand.onCommand();
// 如果是開機功能,要傳送記憶命令
if (this instanceof OpenCommand) {
memoryCommand.onCommand();
}
onCommand();
}
}
經過修改後的程式碼,雖然執行正常,他自己已經感覺到自己挖的坑越來越深,而其他人也出現了懷疑的態度,因為:
1、父類已經開始影響到子類的業務邏輯。
2、基類越來越臃腫:每一次內部的屬性的增加和sendCommand方法的複雜度的上升,都讓基類的變得越來越臃腫,並且基類已經開始越權處理onCommand的邏輯,Command介面已經形同虛設,類的閱讀和維護都開始出現了問題。
3、已經無法通過Command建立命令例項,全部要被替換成ControllerButton,類的語義出現了嚴重的危機。
命令模式登場
經過之前的討論,命令模式終於登場了,於是咔咔咔重構了程式碼:
/**
* 命令的執行者,處於命令發起者和接收者之間,在這個過程中進行處理
*/
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();
// 如果是開機功能,要傳送記憶命令
if (command instanceof OpenCommand) {
memoryCommand.onCommand();
}
// 執行引數命令
command.onCommand();
}
}
修改呼叫的程式碼:
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);
是不是完全被驚豔到了,就是簡單~
現在開始進入正題:什麼是命令模式?
命令模式是把一個操作和他的引數,包裝成一個新的物件去執行。
命令模式有四個部分:命令,接收者,執行者,發起者。
以剛才的demo為例,Command代表命令,接收者是TV,執行者是Switch,發起者也就是客戶端。
從demo中看到,隨著需求的變化,我們的每一次修改都要修改多個類,並且程式碼的成本也很高,於是通過Switch把Command的執行過程包裝了起來,也就是在發起者和接收者之間,隨意我們就可以根據需求,定製執行的過程。
也可以理解成,Switch把基類中有關於Command的功能全部抽取了出來,作為一個獨立模組。
經過命令模式的重構,我們之後的擴充套件和修改,只要不改變open和close的核心功能,只要修改Switch類就可以了,這就是命令模式的優點。
總結
最後我們對命令模式進行一下總結:
1、命令模式是對某一個操作的和其引數的封裝,目的是維護這個操作的過程。
2、命令模式位於發起者和接收者之間,對兩者進行解耦,便於維護。
3、命令模式能夠幫助我們明確類和介面的定義的目的,理解物件導向程式設計。
順便強調一下,過多的if-else是糟糕的程式碼,凸顯出程式的笨重,例如demo中,我們可以利用開關來解決:
/**
* 在基類中增加boolean型開關,並增加引數為ControllerButton的方法
*/
public class Switch {
...
public void excuteCommand(ControllerButton controllerButton) {
// 如果是開機功能,要傳送記憶命令
if (controllerButton.isNeedMemory()) {
memoryCommand.onCommand();
}
excuteCommand(controllerButton);
}
public void excuteCommand(Command command) {
// 指示燈閃閃
lampBulingCommand.onCommand();
// 執行引數命令
command.onCommand();
}
}
最後要說的是,不要隨意違背開發的基本原則,例如上面的那位朋友,這些規則是前輩經過長時間的研究總結的結晶,當然這些經驗不一定是對的,也不適於所有的場景,但是如果你非要這麼做,請做好充足的準備。
相關文章
- 大話設計模式—命令模式設計模式
- 設計模式總結(模式篇)設計模式
- 設計模式總結 —— 單例設計模式設計模式單例
- 設計模式總結設計模式
- 【設計模式 Android】設計模式六大原則設計模式Android
- php設計模式總結-單件模式PHP設計模式
- php設計模式總結-工廠模式PHP設計模式
- 設計模式六大設計原則設計模式
- 設計模式-六大設計原則設計模式
- JavaScript設計模式總結JavaScript設計模式
- PHP設計模式總結PHP設計模式
- 前端設計模式總結前端設計模式
- 【設計模式總結篇】設計模式
- 設計模式--命令模式設計模式
- JS設計模式六:策略模式JS設計模式
- 設計模式(六)——建造者模式設計模式
- 設計模式(六):狀態模式設計模式
- 設計模式(六)策略模式Strategy設計模式
- 建立型設計模式對比總結 設計模式(八)設計模式
- 設計模式大雜燴(24種設計模式的總結及學習設計模式的幾點建議)設計模式
- js設計模式–命令模式JS設計模式
- Java設計模式——命令模式Java設計模式
- js設計模式--命令模式JS設計模式
- 設計模式(五):命令模式設計模式
- 設計模式之-命令模式設計模式
- 設計模式之命令模式設計模式
- C++設計模式 - 總結C++設計模式
- 設計模式-六大原則設計模式
- 設計模式六大原則設計模式
- 設計模式——六大原則設計模式
- JavaScript 設計模式(六) 迭代器模式JavaScript設計模式
- 設計模式(六):裝飾器模式設計模式
- 設計模式總結篇系列:建造者模式(Builder)設計模式UI
- 《設計模式七》備忘錄、模板方法、狀態模式及設計模式設計總結設計模式
- 用到的設計模式總結--單例模式+工廠方法模式+Builder模式設計模式單例UI
- 設計模式-命令模式(Command)設計模式
- 設計模式系列 6– 命令模式設計模式
- Python 設計模式-命令模式Python設計模式