設計模式14—設計模式之命令模式(Command)(行為型)

來源:互聯網
上載者:User
1.情境類比 

請用軟體類比開機過程
按下啟動按鈕
然後電源供電
主板開始加電自檢
BIOS依次尋找其他裝置的BIOS並且讓他們初始化自檢
開始檢測CPU,記憶體,光碟片,硬碟,光碟機,串口,並口,軟碟機隨插即用裝置
進入系統引導

上面的過程可以抽象為如下:
用戶端只是想要發出命令或者請求,不關心請求的真正接受者是誰,也不關心具體如何?,而且同一個請求的動作可以有不同的請求內容,當然具體的處理功能也不一樣。請問如何?呢?
2.使用命令模式來解決問題2.1命令模式定義

將一個請求封裝為一個對象,從而是你可以用不同的請求對客戶進行參數化,隊請求排隊或者請求日誌,以及支援可撤銷的操作。
2.2命令模式的結構圖

在命令模式中,會定義一個命令的介面,用來約束所有的命令對象,然後提供具體的命令實現,每個命令實現對象是對用戶端某個請求的封裝,對應於機箱上的按鈕,一個機箱上可以有很多按鈕,也就相當於會有多個具體的命令實現對象。在命令模式中,命令對象並不知道如何處理命令,會有相應的接受者對象來真正執行命令。

 
3.命令模式範例程式碼詳解3.1命令介面,聲明執行的操作

package demo12.command.example1;/** * 命令介面,聲明執行的操作 */public interface Command {/** * 執行命令對應的操作 */public void execute();}
3.2具體的命令實現對象
package demo12.command.example1;/** * 具體的命令實現對象 */public class ConcreteCommand implements Command {/** * 持有相應的接受者對象 */private Receiver receiver = null;/** * 示意,命令對象可以有自己的狀態 */private String state;/** * 構造方法,傳入相應的接受者對象 * @param receiver 相應的接受者對象 */public ConcreteCommand(Receiver receiver){this.receiver = receiver;}public void execute() {//通常會轉調接受者對象的相應方法,讓接受者來真正執行功能receiver.action();}}
3.3接收者對象
package demo12.command.example1;/** * 接收者對象 */public class Receiver {/** * 示意方法,真正執行命令相應的操作 */public void action(){//真正執行命令操作的功能代碼}}
3.4調用者對象
package demo12.command.example1;/** * 調用者 */public class Invoker {/** * 持有命令對象 */private Command command = null;/** * 設定調用者持有的命令對象 * @param command 命令對象 */public void setCommand(Command command) {this.command = command;}/** * 示意方法,要求命令執行請求 */public void runCommand() {//調用命令對象的執行方法command.execute();}}
3.5用戶端使用
package demo12.command.example1;public class Client {/** * 示意,負責建立命令對象,並設定它的接受者 */public void assemble() {// 建立接受者Receiver receiver = new Receiver();// 建立命令對象,設定它的接收者Command command = new ConcreteCommand(receiver);// 建立Invoker,把命令對象設定進去Invoker invoker = new Invoker();invoker.setCommand(command);}}
4.使用命令模式來實現情境中的問題4.1程式碼範例結構圖

機箱上的按鈕就相當於命令對象
機箱相當於調用者
主板相當於接受者對象
命令對象持有一個接受者對象,相當於給機箱的按鈕連了一根連接線
當機箱上的按鈕被按下的時候,機箱就把這個命令通過連接線發送出去。
 
4.2定義主板介面

package demo12.command.example2;/** * 主板的介面 */public interface MainBoardApi {/** * 主板具有能開機的功能 */public void open();}
4.3技嘉主板
package demo12.command.example2;/** * 技嘉主板類,開機命令的真正實現者,在Command模式中充當Receiver */public class GigaMainBoard implements MainBoardApi{/** * 真正的開機命令的實現 */public void open(){System.out.println("技嘉主板現在正在開機,請等候");System.out.println("接通電源......");System.out.println("裝置檢查......");System.out.println("裝載系統......");System.out.println("機器正常運轉起來......");System.out.println("機器已經正常開啟,請操作");}}
4.4命令介面
package demo12.command.example2;/** * 命令介面,聲明執行的操作 */public interface Command {/** * 執行命令對應的操作 */public void execute();}
4.5命令的具體實現
package demo12.command.example2;/** * 開機命令的實現,實現Command介面, * 持有開機命令的真正實現,通過調用接收者的方法來實現命令 */public class OpenCommand implements Command{/** * 持有真正實現命令的接收者——主板對象 */private MainBoardApi mainBoard = null;/** * 構造方法,傳入主板對象 * @param mainBoard 主板對象 */public OpenCommand(MainBoardApi mainBoard) {this.mainBoard = mainBoard;}public void execute() {//對於命令對象,根本不知道如何開機,會轉調主板對象//讓主板去完成開機的功能this.mainBoard.open();}}
4.6提供機箱
package demo12.command.example2;/** * 機箱對象,本身有按鈕,持有按鈕對應的命令對象 */public class Box {/** * 開機命令對象 */private Command openCommand;/** * 設定開機命令對象 * @param command 開機命令對象 */public void setOpenCommand(Command command){this.openCommand = command;}/** * 提供給客戶使用,接受並相應使用者請求,相當於按鈕被按下觸發的方法 */public void openButtonPressed(){//按下按鈕,執行命令openCommand.execute();}}
4.7用戶端使用
package demo12.command.example2;public class Client {public static void main(String[] args) {//1:把命令和真正的實現組合起來,相當於在組裝機器,//把機箱上按鈕的連接線插接到主板上。MainBoardApi mainBoard = new GigaMainBoard();OpenCommand openCommand = new OpenCommand(mainBoard);//2:為機箱上的按鈕設定對應的命令,讓按鈕知道該幹什麼Box box = new Box();box.setOpenCommand(openCommand);//3:然後類比按下機箱上的按鈕box.openButtonPressed();}}
5.命令模式講解5.1命令模式的關鍵

就是把請求也封裝為對象
命令模式中通常會有一個命令的組裝者,用它來維護虛實現和真實實現之間的關係。
並且發起請求的對象和真正實現的對象是解耦的。
5.2命令模式的調用順序圖如下

 
6.命令的參數化配置

可以用不同的命令對象,去參數化配置客戶的請求。
實際上也很簡單,舉個例子:比如添加一個重啟功能。
那麼就在MainBoardApi上添加一個reset()函數即可,然後再重新寫一個ResetCommand實現Command,當然box中也要添加一個重啟的函數resetButtonPressed()就行啦。那麼用戶端調用就如下

package demo12.command.example3;public class Client {public static void main(String[] args) {//1:把命令和真正的實現組合起來,相當於在組裝機器,//把機箱上按鈕的連接線插接到主板上。MainBoardApi mainBoard = new GigaMainBoard();//建立開機命令OpenCommand openCommand = new OpenCommand(mainBoard);//建立重啟機器的命令ResetCommand resetCommand = new ResetCommand(mainBoard);//2:為機箱上的按鈕設定對應的命令,讓按鈕知道該幹什麼Box box = new Box();//先正確配置,就是開機按鈕對開機命令,重啟按鈕對重啟命令box.setOpenCommand(openCommand);box.setResetCommand(resetCommand);//3:然後類比按下機箱上的按鈕System.out.println("正確配置下------------------------->");System.out.println(">>>按下開機按鈕:>>>");box.openButtonPressed();System.out.println(">>>按下重啟按鈕:>>>");box.resetButtonPressed();//然後來錯誤配置一回,反正是進行參數化配置//就是開機按鈕對重啟命令,重啟按鈕對開機命令box.setOpenCommand(resetCommand);box.setResetCommand(openCommand);//4:然後還是來類比按下機箱上的按鈕System.out.println("錯誤配置下------------------------->");System.out.println(">>>按下開機按鈕:>>>");box.openButtonPressed();System.out.println(">>>按下重啟按鈕:>>>");box.resetButtonPressed();}}
7.可撤銷的操作(重點)

可撤銷操作的意思就是:放棄該操作,回到未執行該操作的狀態。
有兩種基本思路:
1.補嘗試,又稱反操作式。
開啟關閉   加減
2.儲存恢複式
把操作前得狀態記錄下來。
8.補嘗試,又稱反操作式(以計算機為例)8.1操作運算介面(相當於主板介面)

package demo12.command.example4;/** * 操作運算的介面 */public interface OperationApi {/** * 擷取計算完成後的結果 * @return 計算完成後的結果 */public int getResult();/** * 設定計算開始的初始值 * @param result 計算開始的初始值 */public void setResult(int result);/** * 執行加法 * @param num 需要加的數 */public void add(int num);/** * 執行減法 * @param num 需要減的數 */public void substract(int num);}
8.2操作運算真實實作類別(相當於主板)
package demo12.command.example4;/** * 運算類,真正實現加減法運算 */public class Operation implements OperationApi{/** * 記錄運算的結果 */private int result;public int getResult() {return result;}public void setResult(int result) {this.result = result;}public void add(int num){//實現加法功能result += num;}public void substract(int num){//實現減法功能result -= num;}}
8.3抽象命令介面
package demo12.command.example4;/** * 命令介面,聲明執行的操作,支援可撤銷操作 */public interface Command {/** * 執行命令對應的操作 */public void execute();/** * 執行撤銷命令對應的操作 */public void undo();}
8.4真實實現命令類(加命令和減命令)
package demo12.command.example4;/** * 具體的加法命令實現對象 */public class AddCommand implements Command{/** * 持有具體執行計算的對象 */private OperationApi operation = null;/** * 操作的資料,也就是要加上的資料 */private int opeNum;/** * 構造方法,傳入具體執行計算的對象 * @param operation 具體執行計算的對象 * @param opeNum 要加上的資料 */public AddCommand(OperationApi operation,int opeNum){this.operation = operation;this.opeNum = opeNum;}public void execute() {//轉調接收者去真正執行功能,這個命令是做加法this.operation.add(opeNum);}public void undo() {//轉調接收者去真正執行功能//命令本身是做加法,那麼撤銷的時候就是做減法了this.operation.substract(opeNum);}}package demo12.command.example4;/** * 具體的減法命令實現對象 */public class SubstractCommand implements Command{/** * 持有具體執行計算的對象 */private OperationApi operation = null;/** * 操作的資料,也就是要減去的資料 */private int opeNum;/** * 構造方法,傳入具體執行計算的對象 * @param operation 具體執行計算的對象 * @param opeNum 要減去的資料 */public SubstractCommand(OperationApi operation,int opeNum){this.operation = operation;this.opeNum = opeNum;}public void execute() {//轉調接收者去真正執行功能,這個命令是做減法this.operation.substract(opeNum);}public void undo() {//轉調接收者去真正執行功能//命令本身是做減法,那麼撤銷的時候就是做加法了this.operation.add(opeNum);}}
8.5計算機類(相當於機箱)
package demo12.command.example4;import java.util.*;/** * 計算機類,計算機上有加法按鈕、減法按鈕,還有撤銷和恢複的按鈕 */public class Calculator {/** * 命令的操作的記錄,在撤銷時候用 */private List<Command> undoCmds = new ArrayList<Command>();/** * 命令被撤銷的記錄,在恢複時候用 */private List<Command> redoCmds = new ArrayList<Command>();private Command addCmd = null;private Command substractCmd = null;public void setAddCmd(Command addCmd) {this.addCmd = addCmd;}public void setSubstractCmd(Command substractCmd) {this.substractCmd = substractCmd;}public void addPressed(){this.addCmd.execute();//把操作記錄到記錄裡面undoCmds.add(this.addCmd);}public void substractPressed(){this.substractCmd.execute();//把操作記錄到記錄裡面undoCmds.add(this.substractCmd);}public void undoPressed(){if(this.undoCmds.size()>0){//取出最後一個命令來撤銷Command cmd = this.undoCmds.get(this.undoCmds.size()-1);cmd.undo();//如果還有恢複的功能,那就把這個命令記錄到恢複的記錄裡面this.redoCmds.add(cmd );//然後把最後一個命令刪除掉,this.undoCmds.remove(cmd);}else{System.out.println("很抱歉,沒有可撤銷的命令");}}public void redoPressed(){if(this.redoCmds.size()>0){//取出最後一個命令來重做Command cmd = this.redoCmds.get(this.redoCmds.size()-1);cmd.execute();//把這個命令記錄到可撤銷的記錄裡面this.undoCmds.add(cmd);//然後把最後一個命令刪除掉this.redoCmds.remove(cmd);}else{System.out.println("很抱歉,沒有可恢複的命令");}}}
8.6用戶端使用
package demo12.command.example4;public class Client {public static void main(String[] args) {//1:組裝命令和接收者//建立接收者OperationApi operation = new Operation();//建立命令對象,並組裝命令和接收者AddCommand addCmd = new AddCommand(operation,5);SubstractCommand substractCmd = new SubstractCommand(operation,3);//2:把命令設定到持有人,就是計算機裡面Calculator calculator = new Calculator();calculator.setAddCmd(addCmd);calculator.setSubstractCmd(substractCmd);//3:類比按下按鈕,測試一下calculator.addPressed();System.out.println("一次加法運算後的結果為:"+operation.getResult());calculator.substractPressed();System.out.println("一次減法運算後的結果為:"+operation.getResult());//測試撤消calculator.undoPressed();System.out.println("撤銷一次後的結果為:"+operation.getResult());calculator.undoPressed();System.out.println("再撤銷一次後的結果為:"+operation.getResult());//測試恢複calculator.redoPressed();System.out.println("恢複操作一次後的結果為:"+operation.getResult());calculator.redoPressed();System.out.println("再恢複操作一次後的結果為:"+operation.getResult());}}
9.儲存恢複式

先說幾個概念:
宏命令:多個命令的集合的命令
隊列請求:就是對命令對象進行排隊,組成工作隊列,然後依次取出命令對象來執行。
儲存恢複式的要點就在於將請求隊列日誌化,具體的案例我沒有理解的很清楚, 以後遇到合適的例子再重新補充。
10.思考命令模式10.1命令模式本質:

封裝請求
10.2什麼時候選用

需要抽象出需要執行的動作,並參數化這些對象
需要在不同時刻指定排列和執行請求
需要支援取消操作
當支援系統崩潰時,能將系統的操作功能重新執行一遍

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.