1. 程式人生 > 實用技巧 >【設計模式(14)】行為型模式之命令模式

【設計模式(14)】行為型模式之命令模式

個人學習筆記分享,當前能力有限,請勿貶低,菜鳥互學,大佬繞道

如有勘誤,歡迎指出和討論,本文後期也會進行修正和補充


前言

生活中,一件事情的請求者和執行者不一定是同一個人。比如我們申請售後的時候,將需要的資訊等告知售後,然後由售後來安排處理,我們可以去做別的事情了;電視遙控器向電視傳送命令,電視來執行;老闆給我們分配任務等等。。。

好處是我們不需要將請求者與執行者建立繫結關係,也不需要佔用請求者的時間。

在開發中,我們也會需要解除請求者和執行者之間的耦合,一方面利於擴充套件,一方面使得兩者不必同步運作。

命令模式(Command Pattern)是一種資料驅動的設計模式,它屬於行為型模式。

請求以命令的形式包裹在物件中,並傳給呼叫物件。呼叫物件尋找可以處理該命令的合適的物件,並把該命令傳給相應的物件,該物件執行命令。


1.介紹

使用目的:將一個請求封裝為一個物件,使發出請求的責任和執行請求的責任分割開

使用時機:需要將方法的請求者與方法的實現者解耦,進而讓兩者之間通過命令物件進行溝通,方便將命令物件進行儲存、傳遞、呼叫、增加與管理。

解決問題:在某些場合,比如要對行為進行"記錄、撤銷/重做、事務"等處理,這種無法抵禦變化的緊耦合是不合適的

實現方法:請求者通過呼叫者,再由呼叫者來呼叫接受者執行命令。

應用例項:

  • 下載管理器,請求者將請求傳送給管理器,由管理器來執行並管理下載任務
  • 命令池,請求者將命令組裝好後傳入命令池,由命令池執行並管理下載任務

優點

  1. 降低了系統耦合度
  2. 新的命令可以很容易新增到系統中
  3. 釋放了請求者,使其不必等待執行結果

缺點:使用命令模式可能會導致某些系統有過多的具體命令類


2.結構

命令模式包含以下主要角色

  • 抽象命令類(Command)角色:宣告執行命令的介面,擁有執行命令的抽象方法 execute()。
  • 具體命令(Concrete Command)角色:是抽象命令類的具體實現類,它擁有接收者物件,並通過呼叫接收者的功能來完成命令要執行的操作。
  • 實現者/接收者(Receiver)角色:執行命令功能的相關操作,是具體命令物件業務的真正實現者。
  • 呼叫者/請求者(Invoker)角色:是請求的傳送者,它通常擁有很多的命令物件,並通過訪問命令物件來執行相關請求,它不直接訪問接收者。

  • 客戶端呼叫invoker
  • invoker持有Commond物件,並執行execute()命令
  • ConcreteCommand需要實現Commond介面,並持有Receiver物件,在execute()方法中呼叫
  • Receiver則需要定義相關的方法,以供ConcreteCommand呼叫

3.實現步驟

  1. 定義接收者

    class ReceiverA {
        public void action() {
            System.out.println("執行方法A");
        }
    }
    
    class ReceiverB {
        public void action() {
            System.out.println("執行方法B");
        }
    }
    

    接收者裡面負責定義具體需要執行的相關方法

  2. 定義抽象命令類

    interface Command {
        void execute();
    }
    

    用於規範命令類提供的方法介面

  3. 定義具體命令類

    class ConcreteCommandA implements Command {
        private ReceiverA receiver;
    
        public ConcreteCommandA() {
            receiver = new ReceiverA();
        }
    
        @Override
        public void execute() {
            receiver.action();
        }
    }
    
    class ConcreteCommandB implements Command {
        private ReceiverB receiver;
    
        public ConcreteCommandB() {
            receiver = new ReceiverB();
        }
    
        @Override
        public void execute() {
            receiver.action();
        }
    }
    

    此類為核心,需要在此處實現抽象命令類,並持有接收者,並定義命令執行的邏輯

  4. 定義呼叫者

    class Invoker {
        private Command command;
    
        public void setCommand(Command command) {
            this.command = command;
        }
    
        public void call() {
            if (null != command) {
                command.execute();
            }
        }
    }
    

    持有命令物件,並提供設定和執行命令的方法

完整程式碼

package com.company.test.command;

class ReceiverA {
    public void action() {
        System.out.println("執行方法A");
    }
}

class ReceiverB {
    public void action() {
        System.out.println("執行方法B");
    }
}

interface Command {
    void execute();
}

class ConcreteCommandA implements Command {
    private ReceiverA receiver;

    public ConcreteCommandA() {
        receiver = new ReceiverA();
    }

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

class ConcreteCommandB implements Command {
    private ReceiverB receiver;

    public ConcreteCommandB() {
        receiver = new ReceiverB();
    }

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

class Invoker {
    private Command command;

    public void setCommand(Command command) {
        this.command = command;
    }

    public void call() {
        if (null != command) {
            command.execute();
        }
    }
}

public class CommandTest {
    public static void main(String[] args) {
        Invoker invoker = new Invoker();

        Command commandA = new ConcreteCommandA();
        invoker.setCommand(commandA);
        invoker.call();

        Command commandB = new ConcreteCommandB();
        invoker.setCommand(commandB);
        invoker.call();
    }
}

執行結果

4.擴充套件實戰

目標需求如下:

  • 設計一個命令池,被多個命令類共享

  • 客戶端呼叫請求者的方法,由請求者組裝命令,並交付給命令池

  • 命令池會按順序執行接收到的命令

    • 命令按照目標不同進行分組
    • 同一組命令按照先後執行,如果某個命令執行時間超過2s則不再等待,執行下一條命令
    • 每條命令均需要回調,即便超時

實際可能的業務場景如下

  • 專案需要向多個裝置下發命令,通過同一個的命令池管理

  • 對於同一個裝置的命令需要按順序執行,若某條命令完成或者超時則執行下一條,但每條命令均必須有回撥

    因此不考慮超時的情況下,同一裝置的命令為堵塞佇列

  • 對於不同裝置,之間的命令不能堵塞,不能相互影響

  • 系統內的命令只負責呼叫裝置,具體裝置業務由裝置自己執行


測試客戶端程式碼:

public class CommandPoolTest {
    public static void main(String[] args) {
        CommandPool commandPool = new CommandPool();
        CommandInterface command_1 = new CommandImpl(commandPool);
        command_1.doAB();
        command_1.doABC();

        while(true){}
    }
}
  • 一共下發兩條命令,分別是ABABC,那麼全部子命令應該是ABABC
  • 系統中任務等待時間為1秒,超時則執行下一條命令
  • 程式碼中命令AC都是瞬時任務,B為持續3秒的任務,因此B會超時
  • 系統中我們添加了兩個目標,因此每個命令都會發送到這兩個目標

部分執行結果:

  • 第一條命令A正常執行,並得到反饋
  • 第二條命令B下發,但未得到反饋(超時)
  • 第三條命令A下發,並得到反饋

完整的執行結果中,下發命令的順序是ABABC,而收到反饋的順序是AACBB,因為兩次B都是超時完成


完整Demo地址https://gitee.com/echo_ye/practice/tree/master/src/main/java/com/company/commandPool


後記

設計模式只是提供了一種解決某個問題的思路,在實際開發中往往需要多種設計模式協同使用,並且需要根據需求擴充套件和變型

比如這個demo,初略看了一下,使用了命令模式、享元模式、代理模式、單例模式等,還有我也分不清楚了。。。

總之,最終目的就是實現我們的需求,而設計模式則提供瞭解決的思路


作者:Echo_Ye

WX:Echo_YeZ

Email :[email protected]

個人站點:在搭了在搭了。。。(右鍵 - 新建資料夾)