來玩一下Java設計模式之命令模式

wiki上的描述 Encapsulate a request as an object, thereby allowing for the parameterization of clients with different requests, and the queuing or logging of requests. It also allows for the support of undoable operations.java

翻譯意思,把請求封裝成一個對象,從而容許咱們能夠對客戶端的不一樣請求進行參數化,以及對請求進行排隊或記錄。還容許支持撤銷操做。看起來好像很複雜,很難理解。git

通俗簡單理解,它就是將請求封裝成一個對象,在這裏就是這個對象就是命令,而這個命令就是將請求方和執行方分離隔開。從而每個命令其實就是操做,而這樣的流程就是請求方發出請求要求執行某操做,接收方收到請求後並執行對應的操做。這樣下來,請求方和接收方就解耦了,使得請求方徹底不知道接受的操做方法,從也不會知道接收方是什麼時候接受到請求的,又是什麼時候執行操做的,又是怎麼執行操做的。github

具體的角色

Command(抽象命令類):抽象命令類通常是一個抽象類或接口,在其中聲明瞭用於執行請求的execute()等方法,經過這些方法能夠調用請求接收者的相關操做。
ConcreteCommand(具體命令類):具體命令類是抽象命令類的子類,實現了在抽象命令類中聲明的方法,它對應具體的接收者對象,將接收者對象的動做綁定其中。在實現execute()方法時,將調用接收者對象的相關操做(Action)。
Invoker(請求方):調用者即請求發送者,它經過命令對象來執行請求。一個調用者並不須要在設計時肯定其接收者,所以它只與抽象命令類之間存在關聯關係。在程序運行時能夠將一個具體命令對象注入其中,再調用具體命令對象的execute()方法,從而實現間接調用請求接收者的相關操做。
Receiver(接收方):接收者執行與請求相關的操做,它具體實現對請求的業務處理。
Client(客戶端):建立具體命令的對象而且設置命令對象的接受者。

再來看看UML圖

從上方的時序圖中能夠看出運行的順序,Invoker執行execute方法,調用Command1對象,Command1執行action1方法調用Receiver1對象。架構

乾貨代碼

源碼在個人GitHub地址ide

普通的命令模式

如今結合下上回說到的狀態模式一塊兒來實現這個風扇的左轉和右轉功能,此次把他用命令模式來代替以前風扇的轉動,把它當作命令來。學習

客戶端簡單的定義請求方和接收方以及對於的左轉命令和右轉命令,設置命令後對應的執行命令。ui

public class Client {

    public static void main(String[] args) {
        Invoker invoker = new Invoker();
        Receiver receiver = new Receiver();
        Command leftCommand = new LeftCommand(receiver);
        Command rightCommand = new RightCommand(receiver);

        invoker.setCommand(rightCommand);
        invoker.execute();
        invoker.execute();
        invoker.execute();

        invoker.setCommand(leftCommand);
        invoker.execute();
        invoker.execute();
    }
}
複製代碼

請求方spa

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Invoker {

    private Command command;

    public void execute() {
        command.execute();
    }

}
複製代碼

抽象命令線程

public interface Command {

    void execute();
}
複製代碼

開關左轉翻譯

@Data
@AllArgsConstructor
@NoArgsConstructor
public class LeftCommand implements Command {

    private Receiver receiver;

    @Override
    public void execute() {
        receiver.left();
    }
}
複製代碼

開關右轉

@Data
@AllArgsConstructor
@NoArgsConstructor
public class RightCommand implements Command {

    private Receiver receiver;

    @Override
    public void execute() {
        receiver.right();
    }
}
複製代碼

接收方

public class Receiver {

    private Context context = new Context(new CloseLevelState());

    public void left() {
        context.left();
    }

    public void right() {
        context.right();
    }
}
複製代碼

經過命令模式把左轉和右轉封裝成命令,以及以前的狀態模式變動風扇的狀態。本次就是經過狀態模式和命令模式實現了一個風扇開關左右轉的功能。

宏命令或者叫作組合命令

設計一組命令,簡單的處理事情,打印一句話,封裝成一組命令。此次咱們用了Java8來寫,可使用lambda。

@Slf4j
public class Client {

    public static void main(String[] args) {
        Invoker invoker = new Invoker();
        log.info("初始化ABC3個命令");
        Command aCommand = () -> log.info("A處理這個請求");
        invoker.addCommand(aCommand);
        invoker.addCommand(() -> log.info("B處理這個請求"));
        invoker.addCommand(() -> log.info("C處理這個請求"));
        invoker.execute();

        log.info("---------------------------");
        log.info("加入新命令D");
        invoker.addCommand(() -> log.info("D處理這個請求"));
        invoker.execute();

        log.info("---------------------------");
        log.info("加入新命令E");
        invoker.addCommand(() -> log.info("E處理這個請求"));
        invoker.execute();

        log.info("---------------------------");
        log.info("移除命令A");
        invoker.removeCommand(aCommand);
        invoker.execute();
    }
}
複製代碼

打印語句。

抽象命令

@FunctionalInterface
public interface Command {

    void execute();
}
複製代碼

請求方

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Invoker {

    private List<Command> commandList = Lists.newArrayList();

    public void addCommand(Command command) {
        commandList.add(command);
    }

    public void removeCommand(Command command) {
        commandList.remove(command);
    }

    public void execute() {
        if(CollectionUtils.isEmpty(commandList)) {
            return;
        }
        commandList.stream().forEach(command -> command.execute());
    }

}
複製代碼

撤銷操做

在普通的命令模式的基礎上,增長了撤銷操做,在這裏的撤銷操做,其實即爲左轉時的右轉,右轉時的左轉。

@Slf4j
public class Client {

    public static void main(String[] args) {
        Invoker invoker = new Invoker();
        Receiver receiver = new Receiver();
        Command leftCommand = new LeftCommand(receiver);
        Command rightCommand = new RightCommand(receiver);

        invoker.setCommand(rightCommand);
        invoker.execute();
        invoker.execute();
        invoker.execute();
        invoker.undo();
        invoker.undo();

        invoker.setCommand(leftCommand);
        invoker.execute();
        invoker.undo();
    }
}
複製代碼

抽象命令增長了撤銷操做

public interface Command {

    void execute();

    void undo();
}
複製代碼

具體左轉時

@Data
@AllArgsConstructor
@NoArgsConstructor
public class LeftCommand implements Command {

    private Receiver receiver;

    @Override
    public void execute() {
        receiver.left();
    }

    @Override
    public void undo() {
        receiver.right();
    }
}
複製代碼

右轉時

@Data
@AllArgsConstructor
@NoArgsConstructor
public class RightCommand implements Command {

    private Receiver receiver;

    @Override
    public void execute() {
        receiver.right();
    }

    @Override
    public void undo() {
        receiver.left();
    }
}
複製代碼

請求方

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Invoker {

    private Command command;

    public void execute() {
        command.execute();
    }

    public void undo() {
        command.undo();
    }

}
複製代碼

接收方

public class Receiver {

    private Context context = new Context(new CloseLevelState());

    public void left() {
        context.left();
    }

    public void right() {
        context.right();
    }
}
複製代碼

命令模式總結

優勢

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

(2) 新的命令能夠很容易地加入到系統中 。因爲增長新的具體命令類不會影響到其餘類,所以增長新的具體命令類很容易,無須修改原有系統源代碼,甚至客戶類代碼,知足「開閉原則」的要求。在此我向你們推薦一個架構學習交流裙。交流學習裙號:687810532,裏面會分享一些資深架構師錄製的視頻錄像

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

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

缺點

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

適用場景

(1) 系統須要將請求調用者和請求接收者解耦,使得調用者和接收者不直接交互。請求調用者無須知道接收者的存在,也無須知道接收者是誰,接收者也無須關心什麼時候被調用。
(2) 系統須要在不一樣的時間指定請求、將請求排隊和執行請求。一個命令對象和請求的初始調用者能夠有不一樣的生命期,換言之,最初的請求發出者可能已經不在了,而命令對象自己仍然是活動的,能夠經過該命令對象去調用請求接收者,而無須關心請求調用者的存在性,能夠經過請求日誌文件等機制來具體實現。
(3) 系統須要支持命令的撤銷(Undo)操做和恢復(Redo)操做。
(4) 系統須要將一組操做組合在一塊兒造成宏命令。
(5)線程池有一個addTash方法,將任務添加到待完成的隊列中,隊列中的元素就是命令對象,一般的就是一個公共接口,像咱們經常使用的java.lang.Runnable接口。
(6)java8以後,最好在Command接口中@FunctionalInterface修飾,這樣具體的命令就可使用lambda表達式啦。
相關文章
相關標籤/搜索