命令模式 Command Pattern

目的

將訊息(請求)封裝成物件,可以參數化具有不同請求、佇列或日誌請求的Client,並且支援復原動作。

Encapsulate a request as an object (service), thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.

適用

  1. 想用「欲執行之動作來參數化物件。(登記成Callback Function)。
  2. 在不同時間、佇列、執行命令時。
  3. 支援復原動作。
  4. 支援日誌功能,系統Crash時能再重新執行。
  5. 以基礎操作組出高階的系統(常見於支援交易功能的資訊系統中)。

結構及成員

Collaborations:
Client建立ConcreteCommand物件並設定Receiver。
Invoker物件會將ConcreteCommand存起來
Invoker呼叫所存Command之Execute(),若Command是可復原的,ConcreteCommand就會在執行Execute()前先儲存狀態
ConcreteCommand物件呼叫所載Receiver之操作,以真正執行命令。

  • Command:
    • 制訂執行命令之介面。
  • ConcreteCommand: like the CopyCommand, PasteCommand
    • 將Receiver物件和對應的動作繫結起來
    • 實作Execute():呼叫Receiver的對應操作。
  • Client: like the Application
    • 建立ConcreteCommand物件並設定它的Receiver。
    • Client基本上會持有Invoker並使用setCommand設定ConcreteCommand
  • Invoker: like the MenuItem
    • 要求Command執行命令。
  • Receiver: like the Document, Application, etc.
    • 知道如何根收到的訊息執行命令,任何類別都可以是Receiver

影響結果

好處

  • Command將「引發命令的物件」與「知道如何執行的物件」隔離開來。
  • Command是一級物件(First-Class Object),可和一般物件一樣使用及擴充
  • 容易新增新的Command類型,不必更改既有類別。

壞處

  • 會導致大量瑣碎的命令子類別。

實作

Command該有多聰明?

一個極端是只定義訊息接收者和執行動作之間的繫結關係,將任務全部委託給接收者,另一個極端是全都自己來,不轉傳給接收者。

  • 若想把Command與既有類別彼此獨立,或沒有合用的接收者存在,或Command自己知道接收者是誰(如:再產生一個視窗),那可以使用後者。
  • 在這兩個極端中間,Command必須具有足夠的知識動態尋得訊息接收者

支援復原與重做

只要有辦法逆轉執行動作,Command就有復原及重做的能力。故ConcreteCommand可能得存放以下這些狀態:

  • Receiver物件,負責實際執行所要求的命令。
  • 餵給Receiver對應操作的參數。
  • 在執行操作後,所有可能有變動的Receiver原始內容。Receiver必須提供某些操作以供復原。

若想支援一層復原,則必須儲存最後執行的Command;若要多層,就要一個Command History List

置入歷程列表前,若Command執行後狀態會變(如:DeleteCommand),就先複製一份,確保每次啟用的Command都是乾淨的。(i.e. Prototype Pattern)

避免復原過程中不斷累積錯誤

在Command裡多存一點資訊,以確保物件能真的回復到最初狀態。可用Memento Pattern來存。

Example: SimpleRemoteControl

public class Light {

    public Light() {}  
      
    public void on() {  
        System.out.println("Light is on");  
    }
     
    public void off() {  
        System.out.println("Light is off");  
    }
     
}
public class GarageDoor {  
      
    public GarageDoor() {}  
    public void up() {  
        System.out.println("Garage Door is Open");  
    }
  
    public void down() {  
        System.out.println("Garage Door is Closed");  
    }
  
    public void stop() {  
        System.out.println("Garage Door is Stopped");  
    }
  
    public void lightOn() {  
        System.out.println("Garage light is on");  
    }
  
    public void lightOff() {  
        System.out.println("Garage light is off");  
    }
public void Off() {  
        System.out.println("Garage Door is off");  
    }
}
public interface Command { 
    public void execute();  
}
public class GarageDoorOpenCommand implements Command {  
     //Have its suitable receiver
    GarageDoor garageDoor;  
     //Specify the Constructor
    public GarageDoorOpenCommand (GarageDoor garageDoor) { 
        this.garageDoor = garageDoor;  
    }  
      
    //Execute the garageDoor.up() function
    @Override  
    public void execute() { 
        // TODO Auto-generated method stub  
        garageDoor.up();  
    }  
  
} 
//Then describe the LightOnCommand implements Command
public class LightOnCommand implements Command {  
  
    Light light;  
      
    public LightOnCommand (Light light) {  
        this.light = light;  
    }  
      
    @Override  
    public void execute() {  
        // TODO Auto-generated method stub  
        light.on();  
    }  
  
}
public class SimpleRemoteControl {
      
    Command slot;  
      
    public SimpleRemoteControl() {}  
      
	//Describe the important function of the invoker
    public void setCommand(Command command) {  
        this.slot = command;  
    }  
      
    public void buttonWasPressed() {  
        slot.execute();  
    }  
}
public class SimpleRemoteControlClient{

    public static void main(String[] args) {
        Light light = new Light();
        GarageDoor garageDoor = new GarageDoor();
        SimpleRemoteControl remoteControl = new SimpleRemoteControl();

        Command lightOnCommand = new LightOnCommand(light);
        remoteControl.setCommand(lightOnCommand);
        remoteControl.buttonWasPressed();

        Command garageDoorOpenCommand = new GarageDoorOpenCommand(garageDoor );
        remoteControl.setCommand(garageDoorOpenCommand );
        remoteControl.buttonWasPressed();
    }
}
Result:

Light is on
Garage Door is Open