コマンドデザインパターン3(ライトのUndoの実装)

コマンド設計パターンの実現テストします

シナリオ

コマンドデザインパターン2にUndoの基本機能を組み込みます

クラス図

RemoteLoaderコード

リモコンのスロットにロードされる多数のコマンドオブジェクトを作成します
コマンドオブジェクトは、ホームオートメーションデバイスのリクエストをカプセル化します

namespace P209Command
{
    public class RemoteLoader
    {
        public static void Main()
        {
            RemoteLoader remoteLoader = new RemoteLoader();
            remoteLoader.Run();
        }

        public void Run()
        {
            RemoteControlWithUndo remoteControl = new RemoteControlWithUndo();

            // 全てのデバイスをそれぞれ適切な場所に作成します
            Light livingRoomLight = new Light("リビングルーム");

            // 全てのLightコマンドオブジェクトを作成します
            LightOnCommand livingRoomLightOn = new LightOnCommand(livingRoomLight);
            LightOffCommand livingRoomLightOff = new LightOffCommand(livingRoomLight);


            // 全てのコマンドが作成できたので、コマンドをリモコンのスロットにロードできます
            remoteControl.SetCommand(0, livingRoomLightOn, livingRoomLightOff);

            // 問題ないようです
            // 実行準備が整いました!これで各スロットに対するOnボタンとOffボタンを押してテストしましょう
            remoteControl.OnButtonWasPushed(0);
            remoteControl.OffButtonWasPushed(0);

            Console.WriteLine(remoteControl);

            remoteControl.UndoButtonWasPushed();

            remoteControl.OffButtonWasPushed(0);
            remoteControl.OnButtonWasPushed(0);

            Console.WriteLine(remoteControl);

            remoteControl.UndoButtonWasPushed();
        }
    }
}

RemoteControlWithUndoコード

リモートコントロールの実装部分を作成します

using System.Text;

namespace P209Command
{
    public class RemoteControlWithUndo
    {
        /// <summary>
        /// リモコンは7つのOntoOffのコマンドを扱います
        /// コマンドを対応する配列に保持します
        /// </summary>
        ICommand[] onICommands;
        ICommand[] offICommands;
        ICommand undoCommand;

        /// <summary>
        /// コンストラクタでは、OnとOffの配列のインスタンス化と初期化だけが実行されます
        /// </summary>
        public RemoteControlWithUndo()
        {
            onICommands = new ICommand[7];
            offICommands = new ICommand[7];

            ICommand noCommand = new NoCommand();

            for (int i = 0; i < 7; i++)
            {
                onICommands[i] = noCommand;
                offICommands[i] = noCommand;
            }
            undoCommand = noCommand;
        }

        /// <summary>
        /// 後で使用するコマンドをOnとOffの配列に代入します
        /// </summary>
        /// <param name="slot">スロットの位置</param>
        /// <param name="onCommnad">スロットに格納するOnコマンド</param>
        /// <param name="offCommand">スロットに格納するOffコマンド</param>
        public void SetCommand(int slot, ICommand onCommnad, ICommand offCommand)
        {
            onICommands[slot] = onCommnad;
            offICommands[slot] = offCommand;
        }

        /// <summary>
        /// Onボタンを押すと対応するOnButtonWasPushed()メソッドがハードウェアから
        /// 呼び出されます
        /// </summary>
        /// <param name="slot">スロット番号</param>
        public void OnButtonWasPushed(int slot)
        {
            onICommands[slot].Execute();
            undoCommand = onICommands[slot];
        }

        /// <summary>
        /// Offボタンを押すと対応するOffButtonWasPushed()メソッドがハードウェアから
        /// 呼び出されます
        /// </summary>
        /// <param name="slot">スロット番号</param>
        public void OffButtonWasPushed(int slot)
        {
            offICommands[slot].Execute();
            undoCommand = offICommands[slot];
        }

        public void UndoButtonWasPushed()
        {
            undoCommand.Undo();
        }

        public override string ToString()
        {
            StringBuilder stringBuilder = new StringBuilder();

            stringBuilder.Append("\n----- リモコン -----\n");

            for (int i = 0; i < onICommands.Length; i++)
            {
                stringBuilder.Append($"[スロット{i}] {onICommands[i].GetType().Name} {offICommands[i].GetType().Name}\n");
            }

            stringBuilder.Append($"[アンドウ] {undoCommand.GetType().Name}");
            return stringBuilder.ToString();
        }
    }
}

コマンドインターフェイスのコード

全てのRemoteControlコマンドはICommandインターフェースを実装しています
このインターフェースはExecute()とUndo()いうメソッドを持ちます
コマンドは、特定のベンダークラスの一連のアクションをカプセル化します
リモコンは、Execute()メソッドを呼び出してアクションを起動します
また、Undo()メソッドを呼び出してやり直しをします

using System;
namespace P203Command
{
    /// <summary>
    /// ICommand
    /// 全てのコマンドのためのインターフェースを宣言します
    /// コマンドはExecute()メソッドで起動し、Execute()メソッドは、
    /// レシーバーにアクションを実行するように依頼します
    /// このインターフェースには、Undo()メソッドもあります
    /// </summary>
    public interface ICommand
    {
        void Execute();
        void Undo();
    }
}

Lightコード

ベンダークラスを使って、デバイスを制御する実際のホームオートメーション処理を実行します
ここでは例として、Lightクラスを使用しています

namespace P203Command
{
    /// Receiver
    /// リクエストに対処するために必要な処理の実行方法を
    /// 知っています
    /// どのクラスでもReceiverとして動作できます
    public class Light
    {
        private string name;

        public Light(string name)
        {
            this.name = name;
        }

        internal void Off()
        {
            Console.WriteLine($"{name}照明を消しました");
        }

        internal void On()
        {
            Console.WriteLine($"{name}照明を点けました");
        }
    }
}

LightのCommandのコード

ICommandインターフェースを利用して、リモコンのボタンを押すと起動できるアクションを簡単なコマンドオブジェクトで実装します
コマンドオブジェクトはベンダークラスのインスタンスであるオブジェクトへの参照を保持し、そのオブジェクトの1つ以上のメソッドを呼び出すExecute()メソッドとUndo()メソッドを実装します
ここでは照明のOnとOffを行う2つのクラスを表しています

LightOnCommandコード

using System;
namespace P209Command
{
    /// <summary>
    ///  ConcreteCommand
    ///  アクションとReceiverの結びつきを定義します
    ///  InvokerがExecute()メソッドを呼び出してリクエストを行い、
    ///  ConcreteCommandがReceiverの1つ以上の悪所運を呼び出しています
    /// </summary>
    public class LightOnCommand : ICommand
    {
        Light light;

        public LightOnCommand(Light light)
        {
            this.light = light;
        }

        /// <summary>
        /// リクエストの実行に必要なレシーバーのアクションを起動します
        /// </summary>
        public void Execute()
        {
            light.On();
        }

        public void Undo()
        {
            light.Off();
        }
    }
}

LightOffCommandコード

using System;
using System.Collections.Generic;

namespace P209Command
{
    public class LightOffCommand : ICommand
    {
        private Light light;

        public LightOffCommand()
        {
        }

        public LightOffCommand(Light light)
        {
            this.light = light;
        }

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

        public void Undo()
        {
            light.On();
        }
    }
}

結果

リビングルーム照明を点けました
リビングルーム照明を消しました

—– リモコン —–
[スロット0] LightOnCommand LightOffCommand
[スロット1] NoCommand NoCommand
[スロット2] NoCommand NoCommand
[スロット3] NoCommand NoCommand
[スロット4] NoCommand NoCommand
[スロット5] NoCommand NoCommand
[スロット6] NoCommand NoCommand
[アンドウ] LightOffCommand
リビングルーム照明を点けました
リビングルーム照明を消しました
リビングルーム照明を点けました

—– リモコン —–
[スロット0] LightOnCommand LightOffCommand
[スロット1] NoCommand NoCommand
[スロット2] NoCommand NoCommand
[スロット3] NoCommand NoCommand
[スロット4] NoCommand NoCommand
[スロット5] NoCommand NoCommand
[スロット6] NoCommand NoCommand
[アンドウ] LightOnCommand
リビングルーム照明を消しました

結果の説明

照明をつけた後に消します

リビングルーム照明を点けました
リビングルーム照明を消しました

照明コマンドです

[スロット0] LightOnCommand LightOffCommand

アンドウは、最後に呼び出したLightOffCommandを保持します

[アンドウ] LightOffCommand

アンドウが押されました。LightOffCommandのUndo()メソッドが照明を点け直します

リビングルーム照明を点けました

そして照明を消した後に点け直します

リビングルーム照明を消しました
リビングルーム照明を点けました

参考