Design Pattern - Command

(史帝芬, 2007/04/28, hi.steven@gmail.com)
  • 前言

  •     現在我們要設計一個遙控汽車的遙控器,這個遙控器上有八個按鈕, 初期我們只會有四個按鍵是有功能的,其餘四個保留做為未來擴充之用,同時我們希望按鍵上的功 能不是直接設計在遙控器上,這個遙控器現在是用來遙控小汽車,但是要將它改成 電視或其它東西的遙控器,只要裝上不同的按鍵即可。也就是說,遙控器只用來發出命令給實際 執行命令的東西,並不需要知道命令的執行細節。

  • Class Diagram


  • Sequence Diagram


  • 程式1 (C#)

  • using System;
    
    namespace Command
    {
        /// <summary>
        /// Receiver: 接收者實際進行必要的工作。
        /// 這輛遙控汽車有PowerOn、PowerOff、Move(前進)、Stop(煞車)四項功能。
        /// </summary>
        public class Car
        {
            public void PowerOn()
            {
                Console.WriteLine("Power On");
            }
    
            public void PowerOff()
            {
                Console.WriteLine("Power Off");
            }
    
            public void Move()
            {
                Console.WriteLine("Move");
            }
    
            public void Stop()
            {
                Console.WriteLine("Stop");
            }
        }
    
        /// <summary>
        ///  所有命令都要實現的介面
        /// </summary>
        public interface Command
        {
            void execute();
        }
    
        #region Concrete Command (實際的命令)
        public class PowerOnCommand : Command
        {
            private Car car;
    
            public PowerOnCommand(Car car)
            {
                this.car = car;
            }
    
            public void execute()
            {
                car.PowerOn();
            }
        }
    
        public class PowerOffCommand : Command
        {
            private Car car;
    
            public PowerOffCommand(Car car)
            {
                this.car = car;
            }
    
            public void execute()
            {
                car.PowerOff();
            }
        }
    
        public class MoveCommand : Command
        {
            private Car car;
    
            public MoveCommand(Car car)
            {
                this.car = car;
            }
    
            public void execute()
            {
                car.Move();
            }
        }
    
        public class StopCommand : Command
        {
            private Car car;
    
            public StopCommand(Car car)
            {
                this.car = car;
            }
    
            public void execute()
            {
                car.Stop();
            }
        }
    
        public class NoCommand : Command
        {
            public void execute() { }
        }
        #endregion
    
        /// <summary>
        /// Invoker: 啟動者
        /// </summary>
        public class RemoteControl
        {
            private Command[] commands;
    
            public RemoteControl()
            {
                commands = new Command[8];
                for (int i = 0; i < 8; i++)
                {
                    commands[i] = new NoCommand();
                }
            }
    
            public void setCommand(int slot, Command cmd)
            {
                commands[slot] = cmd;
            }
    
            public void PushButton(int slot)
            {
                commands[slot].execute();
            }
        }
    
        /// <summary>
        /// Client: 負責建立ConcreteCommand,並設定其接收者。
        /// </summary>
        class Program
        {
            static void Main(string[] args)
            {
                RemoteControl remoteCtrl = new RemoteControl();
                Car car = new Car();
    
                PowerOnCommand powerOn = new PowerOnCommand(car);
                PowerOffCommand powerOff = new PowerOffCommand(car);
                MoveCommand move = new MoveCommand(car);
                StopCommand stop = new StopCommand(car);
    
                remoteCtrl.setCommand(0, powerOn);
                remoteCtrl.setCommand(1, powerOff);
                remoteCtrl.setCommand(2, move);
                remoteCtrl.setCommand(3, stop);
    
                remoteCtrl.PushButton(0);
                remoteCtrl.PushButton(2);
                remoteCtrl.PushButton(3);
                remoteCtrl.PushButton(1);
    
                Console.ReadLine();
            }
        }
    }
    

  • 執行結果

  • Power On
    Move
    Stop
    Power Off
    

  • 說明1

  •     上面的RemoteControl class完全不知道Command的實作細節, 只知道按下指定的按鍵,至於要發出什麼命令,由Concrete Command去做就可以了, 而Concrete Command雖然會發出指定的命令,對於如何執行也交給Car去做,透過這樣 的設計RemoteControl (Invoker)及Concrete Command看似很笨,卻比較有擴充彈性。 當我們的汽車又增加了「左轉」、「右轉」功能時,只要再加上兩個Concrete Command, 並設定給RemoteControl,並不需要修改既有程式碼 (程式2)。
        程式1中還有一項需特別注意的是,我們還設計了NoCommand, 先將八個按鍵都設定沒有作用,如果遙控器某個沒有功能的按鍵被按下,就會呼叫 NoCommand裡的execute,當然啦~ 這時候什麼事也不會發生。

  • 程式2 (C#)

  • using System;
    
    namespace Command
    {
        /// <summary>
        /// Receiver: 接收者實際進行必要的工作。
        /// 這輛遙控汽車有PowerOn、PowerOff、Move(前進)、Stop(煞車)、TurnLeft(左轉)、TurnRight(右轉)等六項功能。
        /// </summary>
        public class Car
        {
            public void PowerOn()
            {
                Console.WriteLine("Power On");
            }
    
            public void PowerOff()
            {
                Console.WriteLine("Power Off");
            }
    
            public void Move()
            {
                Console.WriteLine("Move");
            }
    
            public void Stop()
            {
                Console.WriteLine("Stop");
            }
    
            #region New Command
            public void TurnLeft()
            {
                Console.WriteLine("Turn Left");
            }
    
            public void TurnRight()
            {
                Console.WriteLine("Turn Right");
            }
            #endregion
        }
    
        /// <summary>
        ///  所有命令都要實現的介面
        /// </summary>
        public interface Command
        {
            void execute();
        }
    
        #region Concrete Command (實際的命令)
        public class PowerOnCommand : Command
        {
            private Car car;
    
            public PowerOnCommand(Car car)
            {
                this.car = car;
            }
    
            public void execute()
            {
                car.PowerOn();
            }
        }
    
        public class PowerOffCommand : Command
        {
            private Car car;
    
            public PowerOffCommand(Car car)
            {
                this.car = car;
            }
    
            public void execute()
            {
                car.PowerOff();
            }
        }
    
        public class MoveCommand : Command
        {
            private Car car;
    
            public MoveCommand(Car car)
            {
                this.car = car;
            }
    
            public void execute()
            {
                car.Move();
            }
        }
    
        public class StopCommand : Command
        {
            private Car car;
    
            public StopCommand(Car car)
            {
                this.car = car;
            }
    
            public void execute()
            {
                car.Stop();
            }
        }
    
        public class NoCommand : Command
        {
            public void execute() { }
        }
    
        #region New Command
        public class TurnLeftCommand : Command
        {
            private Car car;
    
            public TurnLeftCommand(Car car)
            {
                this.car = car;
            }
    
            public void execute()
            {
                car.TurnLeft();
            }
        }
    
        public class TurnRightCommand : Command
        {
            private Car car;
    
            public TurnRightCommand(Car car)
            {
                this.car = car;
            }
    
            public void execute()
            {
                car.TurnRight();
            }
        }		 
    	#endregion
        #endregion
        
        /// <summary>
        /// Invoker: 啟動者
        /// </summary>
        public class RemoteControl
        {
            private Command[] commands;
    
            public RemoteControl()
            {
                commands = new Command[8];
                for (int i = 0; i < 8; i++)
                {
                    commands[i] = new NoCommand();
                }
            }
    
            public void setCommand(int slot, Command cmd)
            {
                commands[slot] = cmd;
            }
    
            public void PushButton(int slot)
            {
                commands[slot].execute();
            }
        }
    
        /// <summary>
        /// Client: 負責建立ConcreteCommand,並設定其接收者。
        /// </summary>
        class Program
        {
            static void Main(string[] args)
            {
                RemoteControl remoteCtrl = new RemoteControl();
                Car car = new Car();
    
                PowerOnCommand powerOn = new PowerOnCommand(car);
                PowerOffCommand powerOff = new PowerOffCommand(car);
                MoveCommand move = new MoveCommand(car);
                StopCommand stop = new StopCommand(car);
    
                remoteCtrl.setCommand(0, powerOn);
                remoteCtrl.setCommand(1, powerOff);
                remoteCtrl.setCommand(2, move);
                remoteCtrl.setCommand(3, stop);
    
                remoteCtrl.PushButton(0);
                remoteCtrl.PushButton(2);
                remoteCtrl.PushButton(3);
                remoteCtrl.PushButton(1);
    
                #region New Command
                TurnLeftCommand turnLeft = new TurnLeftCommand(car);
                TurnRightCommand turnRight = new TurnRightCommand(car);
                remoteCtrl.setCommand(4, turnLeft);
                remoteCtrl.setCommand(5, turnRight);
    
                remoteCtrl.PushButton(0);
                remoteCtrl.PushButton(4);
                remoteCtrl.PushButton(5);
                remoteCtrl.PushButton(1);
                #endregion
    
                Console.ReadLine();
            }
        }
    }
    

  • 執行結果

  • Power On
    Move
    Stop
    Power Off
    Power On
    Turn Left
    Turn Right
    Power Off
    

  • 說明2

  •     為了增加玩家的樂趣,小汽車現在將加入了蛇行的功能,但是我們分析 後發現,蛇行不需真的加入新功能,只需要利用既有功能加以組合就可以蛇行了,這時,我們可以 寫一個組合多項功能的MacroCommand,並修改測試程式如下。

  • 程式3 (C#)

  •     #region Macro Command
        public class MacroCommand : Command
        {       
            private Command[] command;
    
            public MacroCommand(Command[] command)
            {
                this.command = command;
            }
    
            public void execute()
            {
                for(int i=0; i < command.Length; i++) 
                {
                    command[i].execute();
                }
            }
        }
        #endregion
    

        class Program
        {
            static void Main(string[] args)
            {
                RemoteControl remoteCtrl = new RemoteControl();
                Car car = new Car();
    
                PowerOnCommand powerOn = new PowerOnCommand(car);
                PowerOffCommand powerOff = new PowerOffCommand(car);
                MoveCommand move = new MoveCommand(car);
                StopCommand stop = new StopCommand(car);
    
                remoteCtrl.setCommand(0, powerOn);
                remoteCtrl.setCommand(1, powerOff);
                remoteCtrl.setCommand(2, move);
                remoteCtrl.setCommand(3, stop);
    
                remoteCtrl.PushButton(0);
                remoteCtrl.PushButton(2);
                remoteCtrl.PushButton(3);
                remoteCtrl.PushButton(1);
    
                #region New Command
                TurnLeftCommand turnLeft = new TurnLeftCommand(car);
                TurnRightCommand turnRight = new TurnRightCommand(car);
                remoteCtrl.setCommand(4, turnLeft);
                remoteCtrl.setCommand(5, turnRight);
    
                remoteCtrl.PushButton(0);
                remoteCtrl.PushButton(4);
                remoteCtrl.PushButton(5);
                remoteCtrl.PushButton(1);
                #endregion
    
                #region Macro Command
                Command[] cmdSnakeMove = { turnLeft, turnRight, turnRight, turnLeft, move };
                MacroCommand macroCmd = new MacroCommand(cmdSnakeMove);
                remoteCtrl.setCommand(6, macroCmd);
    
                remoteCtrl.PushButton(0);
                remoteCtrl.PushButton(6);
                remoteCtrl.PushButton(3);
                remoteCtrl.PushButton(1);
                #endregion
    
                Console.ReadLine();
            }
        }
    

  • 執行結果

  • Power On
    Move
    Stop
    Power Off
    Power On
    Turn Left
    Turn Right
    Power Off
    Power On
    Turn Left
    Turn Right
    Turn Right
    Turn Left
    Move
    Stop
    Power Off
    

  • 說明3

  •     設計組合命令時,也可以新增一個新的ConcreteCommand, 由這個新的ConcreteCommand的execute去呼叫多個car的命令,但是,這樣的設計會比 較沒有彈性,每次只要新增一個組合命令時,就要多一個ConcreteCommand,我們這裡 採用動態傳入的方式是為了增加彈性。