2013年7月20日 星期六

設計模式:命令模式 (Command Pattern)

命令模式 (Command Pattern),以下程式碼以 C# 為例

說明:
命令包含發出命令和執行命令。
命令模式,則是將這個過程拆成三個物件,
發出命令的物件(Invoker)、命令的物件(command)、執行命令的物件(receiver)
由 Invoker 物件,來建造要執行的命令。
如此要擴充功能時,例如增加命令重覆執行、取消命令...等,也變得較單純。

範例:
有一個機器人物件,會執行三種指令:向前走一步、向左轉、向右轉。

希望達成如下的效果
static void Main(string[] args)
{
    // 初始化各個物件
    Invoker invoker = new Invoker(); // 發命令物件

    ReceiverRobot robot = new ReceiverRobot(); // 執行命令物件

    GoAheadCommand cmd_go_ahead = new GoAheadCommand(robot); // 向前走指令
    TurnLeftCommand cmd_turn_left = new TurnLeftCommand(robot); // 左轉指令
    TurnRightCommand cmd_turn_right = new TurnRightCommand(robot); // 右轉指令

    // 設定要執行的命令
    invoker.SetCommand(cmd_go_ahead);
    invoker.SetCommand(cmd_go_ahead);
    invoker.SetCommand(cmd_turn_left);
    invoker.SetCommand(cmd_go_ahead);
    invoker.SetCommand(cmd_turn_right);

    // 開始執行命令
    invoker.Run();

    Console.Read();
}
執行結果: 
向前走一步
向前走一步
向左轉
向前走一步
向右轉

實現重點在於,分成發命令物件、命令物件、執行命令的物件。
發命令物件:可用來新增要執行命令。
命令物件:可指定執行此命令的物件。
執行命令的物件:真正執行此命令的物件。

其餘程式碼
// 用來發出命令的類別
class Invoker
{
    private IList<Command> cmds = new List<Command>();

    public void SetCommand(Command command)
    {
        cmds.Add(command);
    }

    public void Run()
    {
        foreach (Command command in cmds)
        {
            command.Execute();
        }
    }

}

// 命令的抽像類別,用來衍生各種命令,建構時,須設定實際執行命令的物件
abstract class Command
{
    protected ReceiverRobot robot;

    // 設定實際執行命令的物件
    public Command(ReceiverRobot robot)
    {
        this.robot = robot;
    }

    // 用來呼叫執行命令的物件,開始執行命令
    abstract public void Execute();
}

// 向前走一步的命令
class GoAheadCommand : Command
{
    public GoAheadCommand(ReceiverRobot robot)
        : base(robot)
    {
    }

    public override void Execute()
    {
        robot.GoAhead();
    }
}

// 向左轉的命令
class TurnLeftCommand : Command
{
    public TurnLeftCommand(ReceiverRobot robot)
        : base(robot)
    {
    }

    public override void Execute()
    {
        robot.TurnLeft();
    }
}

// 向右轉的命令
class TurnRightCommand : Command
{
    public TurnRightCommand(ReceiverRobot robot)
        : base(robot)
    {
    }

    public override void Execute()
    {
        robot.TurnRight();
    }
}

// 實際執行命令的物件
class ReceiverRobot
{
    public void GoAhead()
    {
        Console.WriteLine("向前走一步");
    }

    public void TurnLeft()
    {
        Console.WriteLine("向左轉");
    }

    public void TurnRight()
    {
        Console.WriteLine("向右轉");
    }
}

相關連結:設計模式整理列表

6 則留言:

  1. 您好!我看過你上述的範例也測試過,可是小弟這裡有一個問題想要請教,如果我要設計一個動作:
    在自動模式下

    1.相機拍照->2.量測照片->3.結束動作->回到動作1

    在手動模式下

    利用Windows Froms Application建立 1.相機拍照按鈕(執行相機拍照動作) 2.量測照片按鈕(執行量測照片動作)

    現在我困惑的是我介面用Windows Froms Application來寫,因為它好規劃好整理,可是我在網路上找到的一些設計模式的文
    章為利用Console Application來寫,這我就困惑了,如果是這樣那麼要如何將Windows Froms Application與Console Application作資料傳遞與連結,還是Windows Froms Application也是有設計模式的方法呢?

    回覆刪除
    回覆
    1. 您好,網路範例用 Console Application 的原因,
      以我來講,只是為了將重點放在程式邏輯上、以及方便呈現。
      例如,本篇也可以改用 Windows Form 寫,
      然後輸出結果示意文字的地方,改成用 textbox 顯示,
      但這樣文章中可能就要附圖、Windows Form 相關的程式...

      所以理論上,您的需求應該是可以將 Console Application 邏輯改寫成 Windows Form,
      因設計模式只是一種寫法、或說是一種處理問題的想法,用什麼寫影響不大,想法還是那樣。

      其實如果您已寫了易維護的程式,我覺得不一定要拘泥於使用這些設計模式,
      更何況也是有新的設定模式後續被提出。

      當然,如果您是要練習設計模式,就是要先理解,再想怎麼寫成自己要的了。

      刪除
    2. 想請問XYZ先進,您對於C#上的Emgu熟嗎?小弟目前在Emgu上面遇到一些問題!感謝

      刪除
    3. seanhua 您好,不好意思,因平常較少寫C#,也沒碰過Emgu,可能幫不上忙。

      刪除
    4. XYZ先進,感謝您的回覆喔!我再來想想辦法~感謝

      刪除
  2. 先進您好!由於我剛接觸C#不久,所以在一些程式架構的架構上比較不清楚,所以才會找一些範例來做研究再去修改,
    在很多觀念上還是需要向您請益,感謝您的解答,讓我受益良多!

    回覆刪除