命令模式 | Command Pattern

Posted by Ian Tsai on Wednesday, September 30, 2020

本系列文章同步分享於IT邦幫忙第12屆鐵人賽


Design Pattern 系列文章導讀


Design Pattern可以說是開發上大家都會遇到的一個課題, 這系列文會從Design Principles、各種design pattern到最後的Anti-Patterns & Code Smells介紹下去,讓我們可以更了解各種pattern的使用時機與場合。 預計目標主題如下,若有哪部分不熟的章節可以直接點進去看
註:可以利用Online Java Compiler IDE

  1. 設計模式 - 入門篇
  2. 設計模式 - 原則篇 | Design Principles
  3. 設計模式 - 創建型模型篇 | Creational Patterns
  4. 設計模式 - 結構型模型篇 | Structural Patterns
  5. 設計模式 - 行為型模型篇 | Behavioural Patterns
  6. 設計模式 - 番外篇
  • 定義

The Command Pattern encapsulates a request as an object, thereby letting you parameterize other objects with different requests, queue or log requests, and support undoable operations.

翻譯年糕:將一個請求封裝成一個物件,讓你可用不同的請求對客戶進行參數化、對請求排隊或記錄請求日誌,以及支援可取消的操作

這是什麼意思?當A要請求B執行任務時,A會呼叫B然後B在完成任務,在這種情況下A需直接和B進行溝通,就像A是老闆,他要交代員工B去做事情一樣,如下圖。我們稱做A為Sender,B為Reciver。

但如果Sander需要Reciver做很多事怎麼辦?就像是老闆非常忙碌,他沒辦法提醒每個人該做什麼事情,那該怎麼辦?這時老闆可以使用備忘錄。他將任務寫在備忘錄上,再經由秘書把任務交給員工完成。

老闆(Sender)將任務封裝成備忘錄(Command),然後秘書(Invoker)再經由備忘錄的工作事項分派任務給員工(Receiver)。如此一來老闆不需要知道是哪個員工執行,只需要秘書回報任務結果即可。看完這兩張簡易的流程圖,我們對於Command Pattern有了初步的了解。

  • Command Pattern 成員

Command Pattern有幾個組成角色

成員 功用
Command 用來宣告執行操作的interface / abstract class。
ConcreteCommand Command的實體物件,通常會持有Receiver,並呼叫Receiver的功能來完成命令要執行的操作。
Receiver(接收者) 幹活的角色, 命令傳遞到被執行。
Invoker(請求者) 接收並要求執行命令。
Client(裝配者) 建立Command Object,組裝Command Object和Receiver

註:這邊的Clinet並非客戶端的Client,解釋為裝配者較適合,因為使用命令的Client應該是從Invoker來觸發執行

我們可以用下面的UML圖來了解個角色的位置

由表格對應UML可得知,Invoker為呼叫者,負責要求執行命令;Command為命令的介面或抽象類;ConcreteCommand為各種命令實體繼承Command;Receiver為執行者負責執行命令;Client內定義接收者、命令以及呼叫者,接著將命令交給呼叫者執行。

Command Pattern 實作


認識完Command Pattern的角色後,我們將這些角色套用在程式碼內試試看:

Receiver:負責執行命令

class Receiver {
    public void action(String str) {
        System.out.println(str);
    }
}

Command:宣告執行命令的abstract class

abstract class Command{
    Receiver receiver;

    public Command(Receiver receiver){
        this.receiver = receiver;
    }
    public abstract void execute();
}

ConcreteCommand:Command的實體物件,持有並呼叫Receiver的功能來完成命令要執行的操作

class ConcreteCommand extends Command{
    ConcreteCommand(Receiver receiver){
        super(receiver);
    }

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

Invoker:接收並要求執行命令

class Invoker {
    private List<Command> commandList = new ArrayList<>();
    public void setCommand(Command command){
        commandList.add(command);
    }
    public void executeCommand(){
        System.out.println("Invoker call Receiver");
        for (Command command :
                commandList) {
            command.execute();
        }
    }
}

Client

public class CommandPattern {
    public static void main(String[] args) {
        // 定義Receiver
        Receiver receiver = new Receiver();
        // 定義給Invoker的Command(可以多個)
        Command command = new ConcreteCommand(receiver);
        //定義Invoker 
        Invoker invoker = new Invoker();

        System.out.println("Client call Invoker ...");
        // Invoker接收指令
        invoker.setCommand(command);
        // Invoker執行指令
        invoker.executeCommand();
    }
}

output

Client call Invoker ...
Invoker call Receiver
Receiver do action

由上述程式碼,應該更加了解Command Pattern執行的流程,如果還對流程有些模糊,可以參考下面時序圖的流程。


現在我們了解也學會了如何使用Command Pattern,接著就回到老闆及員工的範例,我們來請秘書分擔老闆的事情:

class Employee {
    public void action(String str) {
        System.out.println(str);
    }
}

abstract class Command{
    Employee employee;

    public Command(Employee employee){
        this.employee = employee;
    }
    public abstract void execute();
}

class MeetingCommand extends Command{
    MeetingCommand(Employee employee){
        super(employee);
    }

    @Override
    public void execute(){
        employee.action("Employee do MeetingCommand");
    }
}

class PrepareSlideCommand extends Command{
    PrepareSlideCommand(Employee employee){
        super(employee);
    }

    @Override
    public void execute(){
        employee.action("Employee do PrepareSlideCommand");
    }
}

class Secretary {
    private List<Command> commandList = new ArrayList<>();
    public void setCommand(Command command){
        commandList.add(command);
    }
    public void executeCommand(){
        System.out.println("Secretary call Receiver");
        for (Command command :
                commandList) {
            command.execute();
        }
    }
}

public class CommandPattern {
    public static void main(String[] args) {
        // 定義Receiver
        Employee employee = new Employee();
        // 定義給Invoker的Command(可以多個)
        Command prepareSlide=new PrepareSlideCommand(employee);
        Command meeting=new MeetingCommand(employee);
        //定義Invoker
        Secretary secretary=new Secretary();

        System.out.println("Boss call Secretary  ...");
        // Invoker接收指令
        secretary.setCommand(prepareSlide);
        secretary.setCommand(meeting);
        // Invoker執行指令
        secretary.executeCommand();
    }
}

output

Boss call Secretary  ...
Secretary call Receiver
Employee do PrepareSlideCommand
Employee do MeetingCommand

如此一來老闆就不需要盯著每一個員工做事,他只需要將事情交代給秘書,由秘書去叫員工把事情做好即可。

  • 小結


Command Pattern 的優缺點
優點 缺點
- 降低耦合度 - 易擴充 - 容易設計組合命令 - 若系統需要非常大量的Command,會影像到Command Pattern的使用
Command Pattern 的組成
  • Command: 用來宣告執行操作的interface / abstract class。
  • ConcreteCommand: Command的實體物件,持有並呼叫Receiver執行的操作。
  • Receiver: 執行命令。
  • Invoker: 接收並要求執行命令。
  • Client: 建立Command Object,組裝Command Object和Receiver
Command Pattern 的目標

請求以命令的形式封裝在物件中,並傳給調用對象。調用對象尋找可處理該命令的適合對象,並將命令傳給合適的對象執行。

  • 範例程式碼


範例:Command Pattern 實作

  • References


  • 初探設計模式 - 命令模式 ( Command Pattern )
  • 命令模式
  • 簡說設計模式——命令模式