重溫《深入淺出設計模式》命令模式 (Book Review of Head First Design Pattern, Command Pattern)

2020-07-02

命令模式、外觀模式,乍看之下有些相似,但從模式的意義上的分別兩者就可以感受到不同。命令模式是將命令(要求、需求)封裝成物件,並且讓接受命令者與命令的執行職責拆分,類似餐廳點餐從老闆邊接受客人點餐、邊準備料理分為餐廳前台與餐廳後台,同時命令模式可以將命令與執行時間分隔,延後或者指定執行命令的時間;外觀模式則是將繁雜的類別方法,透過一個簡化的控制類別封裝複雜的演算邏輯,對使用者而言僅需要一個方法就能完成所有關聯的步驟。

logo

命令模式

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

命令模式的 UML

After Pattern

Command 為 Interface 表示將命令的行為封裝為類別,並由衍生類別去實作 Execute。

Command.cs
NoCommand.cs

public interface ICommand
{
    void Execute();
}

internal class NoCommand : ICommand
{
    public void Execute()
    {
        throw new System.NotImplementedException();
    }
}

CeilingFan 為 Receiver 實際的演算邏輯負責者;CeilingFanOffCommand, CeilingFanOnCommand 為實作 ICommand 的 Concrete Command 用來將命令的行為封裝,並且會擁有 Receiver 參考,用以呼叫 Receiver 的方向。

CeilingFan.cs
CeilingFanOffCommand.cs
CeilingFanOnCommand.cs

internal class CeilingFan
{
    private string _location = "";
    private int level;
    public static int HIGH = 2;
    public static int MEDIUM = 1;
    public static int LOW = 0;

    public CeilingFan(string location)
    {
        this._location = location;
    }

    public void High()
    {
        // turns the ceiling fan on to high
        level = HIGH;
        Console.WriteLine((_location + " ceiling fan is on high"));
    }

    public void Medium()
    {
        // turns the ceiling fan on to medium
        level = MEDIUM;
        Console.WriteLine((_location + " ceiling fan is on medium"));
    }

    public void Low()
    {
        // turns the ceiling fan on to low
        level = LOW;
        Console.WriteLine((_location + " ceiling fan is on low"));
    }

    public void Off()
    {
        // turns the ceiling fan off
        level = 0;
        Console.WriteLine((_location + " ceiling fan is off"));
    }

    public int GetSpeed()
    {
        return level;
    }
}

internal class CeilingFanOnCommand : ICommand
{
    private CeilingFan _ceilingFan;

    public CeilingFanOnCommand(CeilingFan ceilingFan)
    {
        this._ceilingFan = ceilingFan;
    }

    public void Execute()
    {
        this._ceilingFan.High();
    }
}

internal class CeilingFanOffCommand : ICommand
{
    private CeilingFan _ceilingFan;

    public CeilingFanOffCommand(CeilingFan ceilingFan)
    {
        this._ceilingFan = ceilingFan;
    }

    public void Execute()
    {
        _ceilingFan.Off();
    }
}

以下都是相關的 ReceiverConcrete Command

GarageDoor.cs
GarageDoorDownCommand.cs
GarageDoorUpCommand.cs

Hottub.cs
HottubOffCommand.cs
HottubOnCommand.cs

Light.cs
LightOffCommand.cs
LightOnCommand.cs
LivingroomLightOffCommand.cs
LivingroomLightOnCommand.cs

Stereo.cs
StereoOffCommand.cs
StereoOnWithCDCommand.cs

TV.cs


RemoteControl 為 Invoker ,負責命令的設定、佇列命令及依序執行。

RemoteControl.cs

public class RemoteControl
{
    private Dictionary<int, ICommand> _onCommands;
    private Dictionary<int, ICommand> _offCommands;

    public RemoteControl()
    {
        _onCommands = new Dictionary<int, ICommand>();
        _offCommands = new Dictionary<int, ICommand>();

        ICommand noCommand = new NoCommand();
        for (int i = 0; i < 7; i++)
        {
            _onCommands[i] = noCommand;
            _offCommands[i] = noCommand;
        }
    }

    public void SetCommand(int slot, ICommand onCommand, ICommand offCommand)
    {
        _onCommands[slot] = onCommand;
        _offCommands[slot] = offCommand;
    }

    public void OnButtonWasPushed(int slot)
    {
        _onCommands[slot].Execute();
    }

    public void OffButtonWasPushed(int slot)
    {
        _offCommands[slot].Execute();
    }
}

RemoteLoader 為 Client ,但意義上不是客戶端,更像是命令模式的環境管理,最主要的責任就是建立 Concrete Command 以及組裝 Receiver。

RemoteLoader.cs

public static void Main(string[] args)
{

  // Client 所負責
  CeilingFan ceilingFan = new CeilingFan("Living Room");
  CeilingFanOnCommand ceilingFanOn = new CeilingFanOnCommand(ceilingFan);
  CeilingFanOffCommand ceilingFanOff = new CeilingFanOffCommand(ceilingFan);

  // Invoker 所負責
  RemoteControl remoteControl = new RemoteControl();
  remoteControl.SetCommand(2, ceilingFanOn, ceilingFanOff);
  Console.WriteLine(remoteControl);

  // 使用者的呼叫命令行為
  remoteControl.OnButtonWasPushed(2);
  remoteControl.OffButtonWasPushed(2);
}

小結

✔️將變動的部分封裝起來
✔️讓需要互動的物件之間的關係鬆綁
✔️類別應該對需求保持開放;應該對既有的程式碼修改保持關閉
命令、接受命令、執行命令都封裝起來,碰到改變互不影響

✔️多用合成,少用繼承
Invoker 合成了 Receiver 與 Concrete Command

✔️針對介面撰寫,而非針對實踐方式撰寫。
Concrete Command 是針對 Command Interface 撰寫,實踐方式則是封裝在 Receiver

參考資料