重識設計模式-命令模式(Command Pattern)

本文已同步發表到個人技術微信公衆號,掃一掃文章底部的二維碼或在微信搜索 「程序員驛站」便可關注,不按期更新優質技術文章。同時,也歡迎加入QQ技術羣(羣號:650306310)一塊兒交流學習!java

定義

命令模式(Command Pattern):將一個請求封裝爲一個對象,從而讓咱們可用不一樣的請求對客戶進行參數化;對請求排隊或者記錄請求日誌,以及支持可撤銷的操做。命令模式是一種對象行爲型模式,其別名爲動做(Action)模式或事務(Transaction)模式。程序員

角色

命令模式的核心在於引入了命令類,經過命令類來下降發送者和接收者的耦合度,請求發送者只需指定一個命令對象,再經過命令對象來調用請求接收者的處理方法,其結構如圖所示:編程

命令模式結構圖

在命令模式結構圖中包含以下幾個角色:
● Command( 抽象命令類) : 抽象命令類通常是一個抽象類或接口,在其中聲明瞭用於執行請求的execute()等方法,經過這些方法能夠調用請求接收者的相關操做。設計模式

● ConcreteCommand( 具體命令類) : 具體命令類是抽象命令類的子類,實現了在抽象命令類中聲明的方法,它對應具體的接收者對象,將接收者對象的動做綁定其中。在實現execute()方法時,將調用接收者對象的相關操做(Action)。bash

● Invoker( 調用者) : 調用者即請求發送者,它經過命令對象來執行請求。一個調用者並不須要在設計時肯定其接收者,所以它只與抽象命令類之間存在關聯關係。在程序運行時能夠將一個具體命令對象注入其中,再調用具體命令對象的execute()方法,從而實現間接調用請求接收者的相關操做。微信

● Receiver( 接收者) : 接收者執行與請求相關的操做,它具體實現對請求的業務處理。學習

命令模式的本質是對請求進行封裝,一個請求對應於一個命令,將發出命令的責任和執行命令的責任分割開。每個命令都是一個操做:請求的一方發出請求要求執行一個操做;接收的一方收到請求,並執行相應的操做。命令模式容許請求的一方和接收的一方獨立開來,使得請求的一方沒必要知道接收請求的一方的接口,更沒必要知道請求如何被接收、操做是否被執行、什麼時候被執行,以及是怎麼被執行的。測試

命令模式的關鍵在於引入了抽象命令類,請求發送者針對抽象命令類編程,只有實現了抽象命令類的具體命令才與請求接收者相關聯。在最簡單的抽象命令類中只包含了一個抽象的execute()方法,每一個具體命令類將一個Receiver類型的對象做爲一個實例變量進行存儲,從而具體指定一個請求的接收者,不一樣的具體命令類提供了execute()方法的不一樣實現,並調用不一樣接收者的請求處理方法。 典型的抽象命令類代碼以下所示:this

/** * Created by Coding小僧 on 2019/3/18 * * @since 1.0 * 抽象命令類 */
public interface Command {
    void execute();
}
複製代碼

對於請求發送者即調用者而言,將針對抽象命令類進行編程,能夠經過構造注入或者設值注入的方式在運行時傳入具體命令類對象,並在業務方法中調用命令對象的execute()方法,其典型代碼以下所示:spa

/** * Created by Coding小僧 on 2019/3/18 * * @since 1.0 */
public class Invoker {
    private Command command;

    /** * 構造注入 */
    public Invoker(Command command) {
        this.command = command;
    }

    /** * 設值注入 */
    public void setCommand(Command command) {
        this.command = command;
    }

    /** * 業務方法,用於調用命令類的execute()方法 */
    public void call() {
        command.execute();
    }
}
複製代碼

具體命令類繼承了抽象命令類,它與請求接收者相關聯,實現了在抽象命令類中聲明的execute()方法,並在實現時調用接收者的請求響應方法action(),其典型代碼以下所示:

/** * Created by Coding小僧 on 2019/3/18 * * @since 1.0 */
public class ConcreteCommand implements Command {
    /** 維持一個對請求接收者對象的引用 */
    private Receiver receiver;

    public ConcreteCommand(Receiver receiver) {
        this.receiver = receiver;
    }

    public void execute() {
        //調用請求接收者的業務處理方法action()
        receiver.action();
    }
}
複製代碼

請求接收者Receiver類具體實現對請求的業務處理,它提供了action()方法,用於執行與請求相關的操做,其典型代碼以下所示:

/** * Created by Coding小僧 on 2019/3/18 * * @since 1.0 */
public class Receiver {
    public void action() {
        //具體操做
    }
}
複製代碼

案例回放

某軟件公司開發人員爲公司內部OA系統開發了一個桌面版應用程序,該應用程序爲用戶提供了一系列自定義功能鍵,用戶能夠經過這些功能鍵來實現一些快捷操做。該軟件公司開發人員經過分析,發現不一樣的用戶可能會有不一樣的使用習慣,在設置功能鍵的時候每一個人都有本身的喜愛,例若有的人喜歡將第一個功能鍵設置爲「打開幫助文檔」,有的人則喜歡將該功能鍵設置爲「最小化至托盤」,爲了讓用戶可以靈活地進行功能鍵的設置,開發人員提供了一個「功能鍵設置」窗口,該窗口界面如圖所示:

「功能鍵設置」界面效果圖

經過上圖所示界面,用戶能夠將功能鍵和相應功能綁定在一塊兒,還能夠根據須要來修改功能鍵的設置,並且系統在將來可能還會增長一些新的功能或功能鍵。

爲了下降功能鍵與功能處理類之間的耦合度,讓用戶能夠自定義每個功能鍵的功能,該軟件公司開發人員使用命令模式來設計「自定義功能鍵」模塊,其核心結構如圖所示:

自定義功能鍵核心結構圖

在上圖中,FBSettingWindow是「功能鍵設置」界面類,FunctionButton充當請求調用者,Command充當抽象命令類,MinimizeCommand和HelpCommand充當具體命令類,WindowHanlder和HelpHandler充當請求接收者。完整代碼以下所示:

FBSettingWindow.java

/** * Created by Coding小僧 on 2019/3/18 * * @since 1.0 * 功能鍵設置窗口類 */
public class FBSettingWindow {
    //窗口標題
    private String title;
    //定義一個ArrayList來存儲全部功能鍵
    private ArrayList<FunctionButton> functionButtons = new ArrayList<>();

    public FBSettingWindow(String title) {
        this.title = title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getTitle() {
        return this.title;
    }

    public void addFunctionButton(FunctionButton fb) {
        functionButtons.add(fb);
    }

    public void removeFunctionButton(FunctionButton fb) {
        functionButtons.remove(fb);
    }

    //顯示窗口及功能鍵
    public void display() {
        System.out.println("顯示窗口:" + this.title);
        System.out.println("顯示功能鍵:");
        for (Object obj : functionButtons) {
            System.out.println(((FunctionButton) obj).getName());
        }
        System.out.println("------------------------------");
    }
}
複製代碼

FunctionButton.java

/** * Created by Coding小僧 on 2019/3/18 * * @since 1.0 * 功能鍵類:請求發送者 */
public class FunctionButton {
    private String name; //功能鍵名稱
    private Command command; //維持一個抽象命令對象的引用

    public FunctionButton(String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }

    //爲功能鍵注入命令
    public void setCommand(Command command) {
        this.command = command;
    }

    //發送請求的方法
    public void onClick() {
        System.out.print("點擊功能鍵:");
        command.execute();
    }
}
複製代碼

Command.java

/** * Created by Coding小僧 on 2019/3/18 * * @since 1.0 * 抽象命令類 */
public interface Command {
    void execute();
}
複製代碼

MinimizeCommand.java

/** * Created by Coding小僧 on 2019/3/18 * * @since 1.0 *最小化命令類:具體命令類 */
public class MinimizeCommand implements Command {
    private WindowHanlder whObj; //維持對請求接收者的引用

    public MinimizeCommand() {
        whObj = new WindowHanlder();
    }

    //命令執行方法,將調用請求接收者的業務方法
    public void execute() {
        whObj.minimize();
    }
}
複製代碼

HelpCommand.java

/** * Created by Coding小僧 on 2019/3/18 * * @since 1.0 * 幫助命令類:具體命令類 */
public class HelpCommand implements Command {
    private HelpHandler hhObj; //維持對請求接收者的引用

    public HelpCommand() {
        hhObj = new HelpHandler();
    }

    //命令執行方法,將調用請求接收者的業務方法
    public void execute() {
        hhObj.display();
    }
}
複製代碼

WindowHanlder.java

/** * Created by Coding小僧 on 2019/3/18 * * @since 1.0 * 窗口處理類:請求接收者 */
public class WindowHanlder {
    public void minimize() {
        System.out.println("將窗口最小化至托盤!");
    }
}
複製代碼

HelpHandler.java

/** * Created by Coding小僧 on 2019/3/18 * * @since 1.0 * 幫助文檔處理類:請求接收者 */
public class HelpHandler {
    public void display() {
        System.out.println("顯示幫助文檔!");
    }
}
複製代碼

Client.java

/** * Created by Coding小僧 on 2019/3/18 * * @since 1.0 * 客戶端測試代碼 */
public class Client {
    public static void main(String args[]) {
        FBSettingWindow fbSettingWindow = new FBSettingWindow("功能鍵設置");
        FunctionButton fb1 = new FunctionButton("功能鍵1");
        FunctionButton fb2 = new FunctionButton("功能鍵1");

        //幫助文檔命令
        Command helpCommand = new HelpCommand();
        //縮小窗口命令
        Command minimizeCommand = new MinimizeCommand();
   
        //將命令對象注入功能鍵
        fb1.setCommand(helpCommand);
        fb2.setCommand(minimizeCommand);

        fbSettingWindow.addFunctionButton(fb1);
        fbSettingWindow.addFunctionButton(fb2);
        fbSettingWindow.display();

        //調用功能鍵的業務方法
        fb1.onClick();
        fb2.onClick();
    }
}
複製代碼

編譯並運行程序,輸出結果以下:

顯示窗口:功能鍵設置
顯示功能鍵:
功能鍵1
功能鍵1
------------------------------
點擊功能鍵:顯示幫助文檔!
點擊功能鍵:將窗口最小化至托盤!
複製代碼

若是須要修改功能鍵的功能,例如某個功能鍵能夠實現「自動截屏」,只須要對應增長一個新的具體命令類,在該命令類與屏幕處理者(ScreenHandler)之間建立一個關聯關係,而後將該具體命令類的對象注入到某個功能鍵便可,原有代碼無須修改,符合「開閉原則」。在此過程當中,每個具體命令類對應一個請求的處理者( 接收者) ,經過向請求發送者注入不一樣的具體命令對象可使得相同的發送者對應不一樣的接收者,從而實現「將一個請求封裝爲一個對象,用不一樣的請求對客戶進行參數化」,客戶端只須要將具體命令對象做爲參數注入請求發送者,無須直接操做請求的接收者。

典型應用

咱們在Android開發中使用的Runnable(在java.lang包下)也是命令模式應用之一,其結構圖以下:

優勢

命令模式的主要優勢以下:
1.下降系統的耦合度。因爲請求者與接收者之間不存在直接引用,所以請求者與接收者之間實現徹底解耦,相同的請求者能夠對應不一樣的接收者,一樣,相同的接收者也能夠供不一樣的請求者使用,二者之間具備良好的獨立性。

2.新的命令能夠很容易地加入到系統中。因爲增長新的具體命令類不會影響到其餘類,所以增長新的具體命令類很容易,無須修改原有系統源代碼,甚至客戶類代碼,知足「開閉原則」的要求。

3.能夠比較容易地設計一個命令隊列或宏命令( 組合命令)。

4.爲請求的撤銷(Undo)和恢復(Redo)操做提供了一種設計和實現方案。

缺點

命令模式的主要缺點以下:
使用命令模式可能會致使某些系統有過多的具體命令類。由於針對每個對請求接收者的調用操做都須要設計一個具體命令類,所以在某些系統中可能須要提供大量的具體命令類,這將影響命令模式的使用。

重點

命令模式是一種使用頻率很是高的設計模式,它能夠將請求發送者與接收者解耦,請求發送者經過命令對象來間接引用請求接收者,使得系統具備更好的靈活性和可擴展性。在基於GUI的軟件開發,不管是在電腦桌面應用仍是在移動應用中,命令模式都獲得了普遍的應用。

使用場景

在如下狀況下能夠考慮使用命令模式:
1.系統須要將請求調用者和請求接收者解耦,使得調用者和接收者不直接交互。請求調用者無須知道接收者的存在,也無須知道接收者是誰,接收者也無須關心什麼時候被調用。

2.系統須要在不一樣的時間指定請求、將請求排隊和執行請求。一個命令對象和請求的初始調用者能夠有不一樣的生命期,換言之,最初的請求發出者可能已經不在了,而命令對象自己仍然是活動的,能夠經過該命令對象去調用請求接收者,而無須關心請求調用者的存在性,能夠經過請求日誌文件等機制來具體實現。

3.系統須要支持命令的撤銷(Undo)操做和恢復(Redo)操做。

4.系統須要將一組操做組合在一塊兒造成宏命令。

關注個人技術公衆號"程序員驛站",不更新技術文章,微信掃一掃下方二維碼便可關注:

相關文章
相關標籤/搜索