HeadFirstデザインパターン 第6章CommandパターンをC#で軽く実装。

自分のお勉強用 : HeadFirstデザインパターン 第6章CommandパターンをC#で軽く実装。


C#のおさらいとCommandパターンの感触を掴むため、
書籍「HeadFirstデザインパターン」の第6章Commandパターンを適当に実装。

ファイル一覧

  • CeilingFan.cs
  • ICommand.cs
  • Light.cs
  • NoCommand.cs
  • Program.cs
  • RemoteControl.cs
  • RemoteLoader.cs

実装

前述のファイル一覧の順番とは異なります。
まずはインターフェイスから。

ICommand.cs

namespace csConsole
{
  interface ICommand
  {
      void Execute();
      void Undo();
  }
}

Light.cs : コンクリートとレシーバーその1

namespace csConsole
{
  class Light // レシーバー部分
  {
      public Light(){}
      public void on() => Console.WriteLine("ライトオン です。");
      public void off() => Console.WriteLine("ライトOFF です。");
  }
  class LightOnCommand : ICommand // コンクリート部分
  {
      Light light;
      public LightOnCommand(Light light) => this.light = light;
      public void Execute() => light.on();
      public void Undo() => light.off();
  }
  class LightOffCommand : ICommand  // コンクリート部分
  {
      Light light;
      public LightOffCommand(Light light) => this.light = light;
      public void Execute() => light.off();
      public void Undo() => light.on();
  }
}

CeilingFan : コンクリートとレシーバーその2

{
  class CeilingFan
  {
    public const int HIGH = 3;
    public const int MEDIUM = 2;
    public const int OFF = 0;
    int speed;

    public CeilingFan() => speed = OFF;
    public void high() => speed = HIGH;
    public void medium() => speed = MEDIUM;
    public int getSpeed() => speed;
  }
  class CeilingFanHighCommand : ICommand
  {
    CeilingFan CeilingFan;
    int prevSpeed;

    public CeilingFanHighCommand(CeilingFan ceilingFan) => CeilingFan = ceilingFan;

    public void Execute()
    {
      this.prevSpeed = CeilingFan.getSpeed();
      CeilingFan.high();
    }

    public void Undo()
    {
      switch (prevSpeed)
      {
        case CeilingFan.HIGH:
          CeilingFan.high();
          break;
        case CeilingFan.MEDIUM:
          CeilingFan.medium();
          break;
      }
    }
  }

  class CeilingFanMediumCommand : ICommand
  {
    CeilingFan CeilingFan;
    int prevSpeed;

    public CeilingFanMediumCommand(CeilingFan ceilingFan) => CeilingFan = ceilingFan;

    public void Execute()
    {
      this.prevSpeed = CeilingFan.getSpeed();
      CeilingFan.medium();
    }

    public void Undo()
    {
      switch (prevSpeed)
      {
        case CeilingFan.HIGH:
          CeilingFan.high();
          break;
        case CeilingFan.MEDIUM:
          CeilingFan.medium();
          break;
      }
    }
  }
}

NoCommand.cs : コンクリートとレシーバーその3

namespace csConsole
{
  class NoCommand : ICommand
  {
    public void Execute() => Console.WriteLine("NoCommand");
    public void Undo() => Console.WriteLine("NoCommand : Undo");
  }
}

invoker : 記事的にはリモコンクラス

namespace csConsole
{
  class RemoteControl
  {
    ICommand[] onCommands;
    ICommand[] offCommands;
    Stack < ICommand > undoCmds; //「<」の前後に半角スペース入れてます。ブログ化したら消えたので。

    // コンストラクタ
    public RemoteControl()
    {
      // 例題通り7個のボタン
      this.onCommands = new ICommand[7];
      this.offCommands = new ICommand[7];
      
      undoCmds = new Stack();

      // 初期化
      ICommand noCommand = new NoCommand();
      for (int i = 0 ; i < 7; i++) 
      { 
        this.onCommands[i]=noCommand;
        this.offCommands[i]=noCommand;
      }
    }
  }
  public void setCommand( int slot, ICommand onCmd, ICommand offCmd ) 
  { 
    this.onCommands[slot]=onCmd;
    this.offCommands[slot]=offCmd;
  } 
  public void onPush( int slot ) 
  { 
    this.onCommands[slot].Execute();
    this.undoCmds.Push(this.onCommands[slot]);
  } 
  public void offPush(int slot) 
  { 
    this.offCommands[slot].Execute();
    this.undoCmds.Push(this.offCommands[slot]);
  } 
  public void UndoPush() 
  { 
    this.undoCmds.Pop().Undo(); 
  } 
}

client : 記事的にはリモートローダー(リモコン定義)

namespace csConsole
{
  class RemoteLoader
  {
    static public void setRemoteControler(RemoteControl remote)
    {
      // コンクリートコマンドの作成
      // Light
      Light light = new Light();
      LightOnCommand lightOnCommand = new LightOnCommand(light);
      LightOffCommand lightOffCommand = new LightOffCommand(light);

      // CeilingFan
      CeilingFan ceilingFan = new CeilingFan();
      CeilingFanHighCommand ceilingFanHighCommand = new CeilingFanHighCommand(ceilingFan);
      CeilingFanMediumCommand ceilingFanMediumCommand = new CeilingFanMediumCommand(ceilingFan);

      // コマンドをリモコンに設定
      remote.setCommand(0, lightOnCommand, lightOffCommand);
      remote.setCommand(1, ceilingFanHighCommand, ceilingFanMediumCommand);
    }
  }
}

プログラムのMainクラス

namespace csConsole
{
  class Program
  {
    static void Main(string[] args)
    {
      // リモコン生成
      RemoteControl remote = new RemoteControl();

      // リモコンの内容設定
      RemoteLoader.setRemoteControler(remote);

      remote.onPush(0);   // ここはLightオン。
      remote.UndoPush();  //  LightオンのUndo = オフ。

      remote.onPush(1);   //HIGH
      remote.offPush(1);  //MEDIUM
      remote.UndoPush();  //MEDIUMオブジェクトの中のUNDOでHIGHを呼ぶ。

      Console.WriteLine("リモコン終了");
    }
  }
}

所感

コードの構造はざっくり把握できたかと。
Undo処理については「Undoとはなにか」という哲学になりそうな感じ。
とりあえずFILOを簡単に使いたかったからStackを選定したけども、
これだと無限にStackするし、実用のときはなんらか工夫が必要ですね。

ところでGoFの本も結城本も、クラス図でClientクラスからInvokerクラスへの関連線が出てないのって
どうしてなんだろうか。
インスタンス持ちますよねぇ。

コメント

このブログの人気の投稿

windows10 で nvidia のグラボのcode43現象を解決した

Java : processbuilder 標準出力 タイムアウト

GTX560Ti がおかしい(code 43が出る)(2018年)→解決しました(2019)