コマンドデザインパターン4(状態を使ってUndoを実装する)

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

シナリオ

コマンドデザインパターン3に状態管理を追加して基本機能を確認します

クラス図

列挙型を使いそれぞれの状態に応じた情報を保持します

RemoteLoaderコード

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

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

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

            CeilingFan ceilingFan = new CeilingFan("リビングルーム");


            // シーリングファン用の強弱を決めるコマンドを作成します
            // ここでは、「強」「中」「Off」の3つのコマンドをインスタンス化します
            CeilingFanMediumCommand ceilingFanMedium = new CeilingFanMediumCommand(ceilingFan);
            CeilingFanHighCommand ceilingFanHigh = new CeilingFanHighCommand(ceilingFan);
            CeilingFanOffCommand ceilingFanOff = new CeilingFanOffCommand(ceilingFan);

            // コマンドが作成できたので、リモコンのスロットにロードできます
            // スロット0に「中」を設定し、スロット1に「強」を設定します
            // また「Off」コマンドもロードします
            remoteControl.SetCommand(0, ceilingFanMedium, ceilingFanOff);
            remoteControl.SetCommand(1, ceilingFanHigh, ceilingFanOff);

            // 実行準備が整いました!これで各スロットに対する強弱を調整してテストしましょう
            // まず、ファンを「中」にします
            remoteControl.OnButtonWasPushed(0);
            // そしてファンを止めます
            remoteControl.OffButtonWasPushed(0);
            Console.WriteLine(remoteControl);
            // アンドウを行います
            // これで「中」に戻るはずです
            remoteControl.UndoButtonWasPushed();

            // 今回はファンを「強」に設定します
            remoteControl.OnButtonWasPushed(1);
            Console.WriteLine(remoteControl);
            // そして再度アンドウを行います
            // これで「中」に戻るはずです
            remoteControl.UndoButtonWasPushed();
        }
    }
}

RemoteControlWithUndoコード

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

using System.Text;

namespace P221Command
{
    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 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();
        }

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

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

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

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

CeilingFanコード

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

namespace P221Command
{
    public class CeilingFan
    {
        public enum Strengths
        {
            OFF,
            LOW,
            MEDIUM,
            HIGH,
        }

        string location;

        public Strengths Speed { get; private set; }

        public CeilingFan(string location)
        {
            this.location = location;
            Speed = Strengths.OFF;
        }

        public void High()
        {
            Speed = Strengths.HIGH;
            Console.WriteLine($"{location}の シーリングファンを「強」にしました");
        }

        public void Medium()
        {
            Speed = Strengths.MEDIUM;
            Console.WriteLine($"{location}の シーリングファンを「中」にしました");
        }

        public void Low()
        {
            Speed = Strengths.LOW;
            Console.WriteLine($"{location}の シーリングファンを「弱」にしました");
        }

        public void Off()
        {
            Speed = Strengths.OFF;
            Console.WriteLine($"{location}の シーリングファンは止まっています");
        }
    }
}

CeilingFanのCommandのコード

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

CeilingFanHighCommandコード

namespace P221Command
{
    public class CeilingFanHighCommand : ICommand
    {
        CeilingFan ceilingFan;
        CeilingFan.Strengths prevSpeed;

        public CeilingFanHighCommand(CeilingFan ceilingFan)
        {
            this.ceilingFan = ceilingFan;
        }

        public void Execute()
        {
            prevSpeed = ceilingFan.Speed;
            ceilingFan.High();
        }

        public void Undo()
        {
            if (prevSpeed == CeilingFan.Strengths.HIGH)
            {
                ceilingFan.High();
            }
            else if (prevSpeed == CeilingFan.Strengths.MEDIUM)
            {
                ceilingFan.Medium();
            }
            else if (prevSpeed == CeilingFan.Strengths.LOW)
            {
                ceilingFan.Low();
            }
            else if (prevSpeed == CeilingFan.Strengths.OFF)
            {
                ceilingFan.Off();
            }
        }
    }
}

CeilingFanOffCommandコード

namespace P221Command
{
    internal class CeilingFanOffCommand : ICommand
    {
        private CeilingFan ceilingFan;
        CeilingFan.Strengths prevSpeed;

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

        public void Execute()
        {
            prevSpeed = ceilingFan.Speed;
            ceilingFan.Off();
        }

        public void Undo()
        {
            if (prevSpeed == CeilingFan.Strengths.HIGH)
            {
                ceilingFan.High();
            }
            else if (prevSpeed == CeilingFan.Strengths.MEDIUM)
            {
                ceilingFan.Medium();
            }
            else if (prevSpeed == CeilingFan.Strengths.LOW)
            {
                ceilingFan.Low();
            }
            else if (prevSpeed == CeilingFan.Strengths.OFF)
            {
                ceilingFan.Off();
            }
        }
    }
}

結果

リビングルームの シーリングファンを「中」にしました
リビングルームの シーリングファンは止まっています

—– リモコン —–
[スロット0] CeilingFanMediumCommand CeilingFanOffCommand
[スロット1] CeilingFanHighCommand CeilingFanOffCommand
[スロット2] NoCommand NoCommand
[スロット3] NoCommand NoCommand
[スロット4] NoCommand NoCommand
[スロット5] NoCommand NoCommand
[スロット6] NoCommand NoCommand
[アンドウ] CeilingFanOffCommand
リビングルームの シーリングファンを「中」にしました
リビングルームの シーリングファンを「強」にしました

—– リモコン —–
[スロット0] CeilingFanMediumCommand CeilingFanOffCommand
[スロット1] CeilingFanHighCommand CeilingFanOffCommand
[スロット2] NoCommand NoCommand
[スロット3] NoCommand NoCommand
[スロット4] NoCommand NoCommand
[スロット5] NoCommand NoCommand
[スロット6] NoCommand NoCommand
[アンドウ] CeilingFanHighCommand
リビングルームの シーリングファンを「中」にしました

参考