常用設計模式系列(十七)—命令模式
第一節、前言
各位老鐵好!
今天我來跟大家分享對象行為型模式第二章節——《命令模式》,“命令”一詞,通俗易懂,我們在生活中經常會發出各種各樣的命令,就像你告訴你手機上的“siri”、“小愛同學”打開音樂、切歌等等命令,而你的“語音小助理”根據不同的指令完成對不同軟件的不同操作,這個過程使用的就是“命令模式”。
第二節、命令模式
命令模式概念:
命令模式(Command Pattern):
將一個請求封裝為一個對象,從而可用不同的請求對客戶進行參數化,對請求排隊或者或者記錄請求日志以及支持可撤銷的操作
命令模式的構成:
個人理解
將用戶的請求即命令進行抽象,抽象為一個對象,每個客戶端調用不同的對象即發起不同的命令來完成指令,命令模式需要支持當請求較多時候的請求隊列排隊,并且記錄請求過程的日志,并且要支持可撤銷的動作。
場景舉例:
在日常生活中,我們使用開關來控制一些電器的打開或者關閉,例如常見的電燈、排風扇、門禁等等,這個開關可以安裝到不同的電器上,那這樣對應安裝的開關就能控制對應的電器,所以開關與電器沒有直接的關系,因為買來的開關可能控制燈泡,可能控制其它電器開關。但是開關與電器之間使用電線連接,開關打開時,電線通電,電器正常工作,相反則電器停止工作,相同的開關可以通過不同的線路來控制不同的電器。這個時候發送者(開關)換個接受者(電器)就可以解耦,發送的對象(開關)只需要知道如何發送請求,而不需要知道如何完成請求(完成請求是電線的職責),這個解耦后運轉的模式,稱為命令模式。
結構圖:
第三節、場景分析及代碼實現
場景分析
我們在使用電腦操控的時候,使用的是鼠標去點擊系統的某個按鈕,按鈕上的字展示的不同的功能,然后點擊按鈕,按鈕會根據不同的指令完成某個功能,例如某個系統有主頁按鈕,登錄按鈕、退出按鈕;
可以分析有如下幾個角色
1.功能按鈕當做調用者。
2.Command類當做抽象命令。
3.可設計三個具體命令類:主頁命令類、登錄命令類、退出命令類。
4.可以設計出一個系統類用來當做接收者,包含訪問主頁命令、登錄命令、退出命令
UML圖
代碼實現
1.調用者功能按鈕類FunctionButton
package com.yang.command;/*** @ClassName FunctionButton* @Description 功能按鈕類* @Author IT小白架構師之路* @Date 2021/1/25* @Version 1.0**/
public class FunctionButton {/*** 命令*/private Command command;public FunctionButton(Command command){this.command = command;}/*** 點擊動作*/public void click(){//執行命令command.execute();}
}
2.抽象命令類Command
package com.yang.command;/*** @ClassName Command* @Description 抽象命令類* @Author IT小白架構師之路* @Date 2021/1/25* @Version 1.0**/
public interface Command {/*** 執行命令*/public void execute();
}
3.訪問主頁命令IndexCommand
package com.yang.command;/*** @ClassName IndexCommand* @Description 訪問主頁命令類* @Author IT小白架構師之路* @Date 2021/1/25* @Version 1.0**/
public class IndexCommand implements Command{/*** 系統類*/private SystemMenu systemMenu;public IndexCommand(){systemMenu = new SystemMenu();}@Overridepublic void execute() {systemMenu.toIndex();}
}
4.登錄命令類LoginCommand
package com.yang.command;/*** @ClassName LoginCommand* @Description 登錄命令類* @Author IT小白架構師之路* @Date 2021/1/25* @Version 1.0**/
public class LoginCommand implements Command{/*** 系統類*/private SystemMenu systemMenu;public LoginCommand(){systemMenu = new SystemMenu();}@Overridepublic void execute() {systemMenu.toLogin();}
}
5.退出命令類LogoutCommand
package com.yang.command;/*** @ClassName LogOutCommand* @Description 退出命令類* @Author IT小白架構師之路* @Date 2021/1/25 14:51* @Version 1.0**/
public class LogOutCommand implements Command{/*** 系統類*/private SystemMenu systemMenu;public LogOutCommand(){systemMenu = new SystemMenu();}@Overridepublic void execute() {systemMenu.toLogOut();}
}
6.系統類作為調用者SystemMenu
package com.yang.command;/*** @ClassName SystemMenu* @Description 系統菜單類* @Author IT小白架構師之路* @Date 2021/1/25* @Version 1.0**/
public class SystemMenu {/*** 訪問主頁*/public void toIndex(){System.out.println("當前是主頁");}/*** 登錄動作*/public void toLogin(){System.out.println("登錄成功");}/*** 退出動作*/public void toLogOut(){System.out.println("您已退出");}
}
7.創建客戶端進行測試
package com.yang.command;/*** @ClassName Client* @Description 客戶端* @Author IT小白架構師之路* @Date 2021/1/25* @Version 1.0**/
public class Client {public static void main(String[] args) {//要發起訪問主頁命令Command command = new IndexCommand();//功能按鈕FunctionButton functionButton = new FunctionButton(command);//點擊functionButton.click();System.out.println("-------------------我是分割線-----------------------");//要發起登錄命令command = new LoginCommand();//功能按鈕functionButton = new FunctionButton(command);//點擊functionButton.click();System.out.println("-------------------我是分割線-----------------------");//要發起登出命令command = new LogOutCommand();//功能按鈕functionButton = new FunctionButton(command);//點擊functionButton.click();System.out.println("-------------------我是分割線-----------------------");}
}
8.測試結果如下
當前是主頁
-------------------我是分割線-----------------------
登錄成功
-------------------我是分割線-----------------------
您已退出
-------------------我是分割線-----------------------
使用代碼實現隊列
加隊列的目的是,當業務發送方為多個、業務接收方為多個時,可以使用隊列存儲多個命令對象,不同的命令對象可以對應不同的請求者。增加隊列后,可以將命令存放在隊列中,一次執行,類似批量執行。
1.創建命令隊列類
package com.yang.command;import java.util.ArrayList;/*** @ClassName CommandQueue* @Description 命令隊列* @Author IT小白架構師之路* @Date 2021/1/25* @Version 1.0**/
public class CommandQueue {//隊列private ArrayList<Command> commands = new ArrayList<Command>();//添加命令public void addCommand(Command command){commands.add(command);}//刪除命令public void delCommand(Command command){commands.remove(command);}//執行public void execute(){for (Command command : commands){command.execute();}}
}
2.改造調用者為隊列版FunctionButtonQueue
package com.yang.command;/*** @ClassName FunctionButtonQueue* @Description 調用者隊列版* @Author IT小白架構師之路* @Date 2021/1/25* @Version 1.0**/
public class FunctionButtonQueue {//隊列對象private CommandQueue commandQueue;public FunctionButtonQueue(CommandQueue commandQueue){this.commandQueue = commandQueue;}//處理public void click(){commandQueue.execute();}
}
3.客戶端測試
package com.yang.command;/*** @ClassName ClientQueue* @Description 隊列版測試* @Author IT小白架構師之路* @Date 2021/1/25* @Version 1.0**/
public class ClientQueue {public static void main(String[] args) {CommandQueue commandQueue = new CommandQueue();//要發起訪問主頁命令Command command = new IndexCommand();commandQueue.addCommand(command);//要發起登錄命令command = new LoginCommand();commandQueue.addCommand(command);//要發起登出命令command = new LogOutCommand();commandQueue.addCommand(command);//執行FunctionButtonQueue functionButtonQueue = new FunctionButtonQueue(commandQueue);functionButtonQueue.click();}
}
4.執行結果
當前是主頁
登錄成功
您已退出
增加命令緩存來完成撤銷
1.增加臨時緩存類CommandCache
package com.yang.command;/*** @ClassName CommandCache* @Description 執行緩存* @Author IT小白架構師之路* @Date 2021/1/25* @Version 1.0**/
public class CommandCache {private static Command lastCommad;private static Command currentCommand;public static Command getLastCommad() {return lastCommad;}public static void setLastCommad(Command lastCommad) {CommandCache.lastCommad = lastCommad;}public static Command getCurrentCommand() {return currentCommand;}public static void setCurrentCommand(Command currentCommand) {CommandCache.currentCommand = currentCommand;}
}
2.優化功能按鈕類
package com.yang.command;/*** @ClassName FunctionButton* @Description 功能按鈕類* @Author IT小白架構師之路* @Date 2021/1/25* @Version 1.0**/
public class FunctionButton {/*** 命令*/private Command command;public FunctionButton(Command command){this.command = command;}/*** 點擊動作*/public void click(){Command lastCommad = CommandCache.getLastCommad();Command currentCommand = CommandCache.getCurrentCommand();//都為空則為本次,第一次撤銷無意義if(null == lastCommad && null == currentCommand){CommandCache.setCurrentCommand(command);CommandCache.setLastCommad(command);}else{//你不為空則把上次的最后一次進行執行CommandCache.setLastCommad(currentCommand);CommandCache.setCurrentCommand(command);}//執行命令command.execute();}public void revert(){System.out.println("--------撤銷開始------");Command lastCommad = CommandCache.getLastCommad();lastCommad.execute();System.out.println("--------撤銷結束------");}
}
3.測試撤銷Client
package com.yang.command;/*** @ClassName RevertClient* @Description 注釋* @Author IT小白架構師之路* @Date 2021/1/25* @Version 1.0**/
public class RevertClient {public static void main(String[] args) {//要發起訪問主頁命令Command command = new IndexCommand();//功能按鈕FunctionButton functionButton = new FunctionButton(command);//點擊functionButton.click();System.out.println("-------------------我是分割線-----------------------");functionButton.revert();//要發起登錄命令command = new LoginCommand();//功能按鈕functionButton = new FunctionButton(command);//點擊functionButton.click();System.out.println("-------------------我是分割線-----------------------");//撤銷functionButton.revert();}
}
4.測試結果,完成了撤銷操作,回到了上一個界面
當前是主頁
-------------------我是分割線-----------------------
--------撤銷開始------
當前是主頁
--------撤銷結束------
登錄成功
-------------------我是分割線-----------------------
--------撤銷開始------
當前是主頁
--------撤銷結束------
第四節
優缺點及適用場景
優點
1.可以降低系統的耦合度,由于請求者和接收者之間不存在直接調用,則請求者與接收者之間做到了完全解耦,相同的請求者也可以做到調用不同的接受者,同樣接收者可以給不同的請求者使用,互相獨立,故降低了耦合度。
2.新的命令可以更快的增加到系統中,創建新的具體命令類不會影響其他類
3.可以簡單的設計出一個隊列命令進行批量執行
4.為撤銷和恢復提供了一種設計和實現方案
缺點
1.當命令逐漸增加時,系統會增加很多的具體命令類。
2.當命令增多時,開發人員維護成本變高
適用場景
1.系統需要將調用者與接受者進行解耦,不需要兩者進行交互的場景下。
2.系統要在不同的時間指定請求,將請求進行排隊或者批量執行是。
3.系統需要增加通用的撤銷、恢復功能時。
4.系統需要將一組操作指令進行抽象,完成通用設計時。
掃描二維碼
關注我吧
IT小白架構師之路