15. Command


Posted by WayneCheng on 2021-01-22

關於 Command 本篇將討論以下幾個問題

1. 關於 Command

2. UML

3. 將 UML 轉為程式碼

4. 情境


測試環境:

OS:Windows 10
IDE:Visual Studio 2019


1. 關於 Command

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

by Gang of Four

  • 將請求封裝為物件,可藉由不同的請求(e.g. Queue、Log requests),對呼叫端請求參數化,並支援取消操作

Command(命令)屬於行為型(Behavioral Patterns),當遇到想將不同工作參數化傳入並加入觸發邏輯時,可使用 Command 來將工作與觸發邏輯切割開來,調用端不需要知道實作細節即可呼叫。

優點:

  • 符合 單一職責原則(Single Responsibility Principle)
  • 符合 開閉原則(Open Closed Principle)

缺點:

  • 由於職責拆分較細,class 數量增加會造成整體程式複雜度提升

2. UML

Class 間關聯:

  • Client 依賴 ConcreteCommand
  • Client & ConcreteCommand 關聯 Receiver
  • ConcreteCommand 繼承 Command
  • Invoker 可包含 Command

Class:

  • Client:呼叫端
  • Receiver:被呼叫端
  • Command:命令的抽象類別或介面
  • ConcreteCommand:命令實作
  • Invoker:儲存與調用命令

3. 將 UML 轉為程式碼

命令介面

/// <summary>
/// 命令介面
/// </summary>
public interface ICommand
{
    public void Execute();
}

命令實作

/// <summary>
/// 命令實作
/// </summary>
public class ConcreteCommand : ICommand
{
    public Receiver _receiver { get; }
    public ConcreteCommand(Receiver receiver)
    {
        _receiver = receiver;
    }

    public void Execute()
    {
        _receiver.Action();
    }
}

被呼叫端

/// <summary>
/// 被呼叫端
/// </summary>
public class Receiver
{
    public void Action()
    {
        Console.WriteLine("Called Receiver.Action()");
    }
}

儲存與調用命令

/// <summary>
/// 儲存與調用命令
/// </summary>
public class Invoker
{
    private ICommand _command { get; set; }

    public void SetCommand(ICommand command)
    {
        _command = command;
    }

    public void RemoveCommand()
    {
        _command = null;
    }

    public void ExecuteCommand()
    {
        _command.Execute();
    }
}
  1. command中加載receiver
  2. invoker中加載command並執行
static void Main(string[] args)
{
    Default.Receiver receiver = new Default.Receiver();
    Default.ICommand command = new Default.ConcreteCommand(receiver);
    Default.Invoker invoker = new Default.Invoker();

    invoker.SetCommand(command);
    invoker.ExecuteCommand();

    Console.ReadLine();
}

執行結果

Called Receiver.Action()

4. 情境

我們接到了一個線上下訂咖啡、餐盒的需求

  • 咖啡是由飲料部區的員工負責
  • 餐盒是由熟食部的負責
  • 顧客都是從同一介面下訂,由系統自動通知對應部門

點餐介面

/// <summary>
/// 點餐介面
/// </summary>
public interface IOrder
{
    public void Execute();
}

點咖啡實作 & 點餐盒實作

/// <summary>
/// 點咖啡實作
/// </summary>
public class OrderCoffee : IOrder
{
    public Employee Employee { get; }
    public OrderCoffee(Employee employee)
    {
        Employee = employee;
    }

    public void Execute()
    {
        Employee.Coffee();
    }
}

/// <summary>
/// 點餐盒實作
/// </summary>
public class OrderBoxedMeal : IOrder
{
    public Employee Employee { get; }
    public OrderBoxedMeal(Employee employee)
    {
        Employee = employee;
    }

    public void Execute()
    {
        Employee.BoxedMeal();
    }
}

員工,包含熟食部 & 飲料部

/// <summary>
/// 員工,包含熟食部 & 飲料部
/// </summary>
public class Employee
{
    // 咖啡
    public void Coffee()
    {
        Console.WriteLine("飲料部收到 咖啡 訂單");
    }

    // 餐盒
    public void BoxedMeal()
    {
        Console.WriteLine("熟食部收到 餐盒 訂單");
    }
}

儲存與調用命令

/// <summary>
/// 儲存與調用命令
/// </summary>
public class Invoker
{
    private IOrder _Order { get; set; }

    public void SetOrder(IOrder order)
    {
        _Order = order;
    }

    public void RemoveOrder()
    {
        _Order = null;
    }

    public void ExecuteCommand()
    {
        _Order.Execute();
    }
}
  1. employee加載到點咖啡orderCoffee & 點餐盒orderBoxedMeal
  2. invoker加載點咖啡 & 點餐盒並執行點餐
static void Main(string[] args)
{
    Situation.Employee employee = new Situation.Employee();
    Situation.IOrder orderCoffee = new Situation.OrderCoffee(employee);
    Situation.IOrder orderBoxedMeal = new Situation.OrderBoxedMeal(employee);
    Situation.Invoker invoker = new Situation.Invoker();

    invoker.SetOrder(orderCoffee);
    invoker.ExecuteCommand();
    invoker.SetOrder(orderBoxedMeal);
    invoker.ExecuteCommand();

    Console.ReadLine();
}

執行結果

飲料部收到 咖啡 訂單
熟食部收到 餐盒 訂單

完整程式碼

GitHub:Behavioral_02_Command


總結

Design Patterns 的說明中還提到了 Queue、Log requests 還有取消操作,關於這幾個部分屬於複雜情況的應用,故本篇並未提及,可以在了解本篇的 Command 基本架構與概念之後,再去找找相關內容來閱讀,會上手得更加快速。


參考資料

  1. Design Patterns
  2. 大話設計模式
  3. dofactory
  4. Refactoring.Guru

新手上路,若有錯誤還請告知,謝謝


#designpattern #CSharp







Related Posts

用 Nest.js 開發 API 吧 (三) - Controller

用 Nest.js 開發 API 吧 (三) - Controller

[自我] 理財規劃

[自我] 理財規劃

JAVA - 物件導向入門

JAVA - 物件導向入門


Comments