まさるblog

越後在住子持ちプログラマー奮闘記 - Author:まさる(高野 将、TAKANO Sho)

目次

Blog 利用状況

ニュース

著書

2010/7発売


Web掲載記事

@IT

.NET開発を始めるVB6プログラマーが知るべき9のこと

CodeZine

実例で学ぶASP.NET Webフォーム業務アプリケーション開発のポイント

第1回 3層データバインドを正しく活用しよう(前編)

ブログパーツ


書庫

日記カテゴリ

コミュニティ

デザインパターンを学ぶ~その23:Commandパターン(3)~

デザインパターンを学ぶ~その21:Commandパターン(1)~

デザインパターンを学ぶ~その22:Commandパターン(2)~

と続いたCommandパターンの最後は、Undo機能を実装してみましょう。手順は次の通り。

 

  1. Do、UndoメソッドをもつIUndoableCommandインターフェイスを定義します。
  2. public interface IUndoableCommand
    {
      void Do();
      void Undo();
    }
    
  3. Receiverを定義します。今回はサンプルとしてCounterクラスを考えます。
  4. public class Counter
    {
      public Counter(int initialValue)
      {
        Count = initialValue;
      }
    
      public void Up()
      {
        Count++;
      }
    
      public void Down()
      {
        Count--;
      }
    
      public int Count
      {
        get;
        private set;
      }
    }
    
  5. Counterクラスに対し、カウントアップ、ダウンを行うCommandクラスを定義します。
  6. public class CountUpCommand : IUndoableCommand
    {
      private Counter counter;
    
      public CountUpCommand(Counter counter)
      {
        this.counter = counter;
      }
    
      public void Do()
      {
        counter.Up();
      }
    
      public void Undo()
      {
        counter.Down();
      }
    }
    
    public class CountDownCommand : IUndoableCommand
    {
      private Counter counter;
    
      public CountDownCommand(Counter counter)
      {
        this.counter = counter;
      }
    
      public void Do()
      {
        counter.Down();
      }
    
      public void Undo()
      {
        counter.Up();
      }
    }
    
  7. Undoに備えて何もしないNullCommandクラスも定義します。
  8. public class NullCommand : IUndoableCommand
    {
      public void Do()
      {
      }
    
      public void Undo()
      {
      }
    }
    
  9. Invokerとして、CountManagerクラスを定義します。
  10. public class CountManager
    {
      private IUndoableCommand upCommand;   // カウントアップ用Command
      private IUndoableCommand downCommand; // カウントダウン用Command
      private IUndoableCommand undoCommand; // Undo用Command
    
      public CountManager(IUndoableCommand upCommand, IUndoableCommand downCommand)
      {
        // 引数で渡されたCommandをフィールド設定
        this.upCommand = upCommand;
        this.downCommand = downCommand;
        
        // 最初はUndo操作をしても何もしないので、
        // NullCommandをundoCommandに設定
        this.undoCommand = new NullCommand();
      }
    
      public void DoUp()
      {
        // カウントアップ実行
        upCommand.Do();
        // 実行したCommandをundoCommandに設定
        undoCommand = upCommand;
      }
    
      public void DoDown()
      {
        // カウントダウン実行
        downCommand.Do();
        // 実行したCommandをundoCommandに設定
        undoCommand = downCommand;
      }
    
      public void Undo()
      {
        // Undo実行
        undoCommand.Undo();
        // Undoを連続して行えないよう、
        // NullCommandを設定
        undoCommand = new NullCommand();
      }
    }
    

では、実行してみましょう。実行用コードは以下の通りです。

class Program
{
  static void Main(string[] args)
  {
    // 新規カウンター(Receiver)作成
    Counter counter = new Counter(10);
    Console.WriteLine(counter.Count);

    // カウントアップ、ダウン用Commandクラスのインスタンス作成
    CountUpCommand cuc = new CountUpCommand(counter);
    CountDownCommand cdc = new CountDownCommand(counter);

    // CountManager(Invoker)を作成
    CountManager cm = new CountManager(cuc, cdc);

    // カウントアップ
    cm.DoUp();
    Console.WriteLine(counter.Count);

    // カウントダウン
    cm.DoDown();
    Console.WriteLine(counter.Count);

    // Undo
    cm.Undo();
    Console.WriteLine(counter.Count);

    // Undo
    cm.Undo();
    Console.WriteLine(counter.Count);
  }
}

実行結果は以下の通り。

image

一度目のUndoでカウンターが元に戻り、二度目のUndoではカウンターに変化がないことが確認できます。

 

さて、ここまでのやり方は一度しかUndoが出来ないものでした。これを複数回Undo可能にするにはどうすればいいのでしょうか?

実はかなり簡単です。Invokerの中にStackとして実行したCommandを保持して、PopしながらUndoを行えばいいだけです。以下は、複数回のUndoを可能にしたCountManagerMultiUndoableです。

public class CountManagerMultiUndoable
{
  private IUndoableCommand upCommand;           // カウントアップ用Command
  private IUndoableCommand downCommand;         // カウントダウン用Command
  private Stack<IUndoableCommand> undoCommands; // Undo用CommandのStack

  public CountManagerMultiUndoable(IUndoableCommand upCommand, IUndoableCommand downCommand)
  {
    // 引数で渡されたCommandをフィールド設定
    this.upCommand = upCommand;
    this.downCommand = downCommand;

    // Stackを初期化
    undoCommands = new Stack<IUndoableCommand>();
  }

  public void DoUp()
  {
    // カウントアップ実行
    upCommand.Do();
    // 実行したCommandをStackにPush
    undoCommands.Push(upCommand);
  }

  public void DoDown()
  {
    // カウントダウン実行
    downCommand.Do();
    // 実行したCommandをStackにPush
    undoCommands.Push(downCommand);
  }

  public void Undo()
  {
    if (undoCommands.Count != 0)
    {
      // Stackが空でなければ、PopしてUndo実行
      var undoCommand = undoCommands.Pop();
      undoCommand.Undo();
    }
  }
}

では、実行してみましょう。先ほどと違って、Undoを3回行うようにしてみます。

class Program
{
  static void Main(string[] args)
  {
    // 新規カウンター(Receiver)作成
    Counter counter = new Counter(10);
    Console.WriteLine(counter.Count);

    // カウントアップ、ダウン用Commandクラスのインスタンス作成
    CountUpCommand cuc = new CountUpCommand(counter);
    CountDownCommand cdc = new CountDownCommand(counter);

    // CountManager(Invoker)を作成
    CountManagerMultiUndoable cm = new CountManagerMultiUndoable(cuc, cdc);

    // カウントアップ
    cm.DoUp();
    Console.WriteLine(counter.Count);

    // カウントダウン
    cm.DoDown();
    Console.WriteLine(counter.Count);

    // Undo
    cm.Undo();
    Console.WriteLine(counter.Count);

    // Undo
    cm.Undo();
    Console.WriteLine(counter.Count);

    // Undo
    cm.Undo();
    Console.WriteLine(counter.Count);
  }
}

実行結果は以下の通りです。

image

2度Undoが行われ、3度目は何もカウンターに変化がないことが確認できます。

 

ここまで3回にわたりCommandパターンを紹介しましたが、いかがでしたでしょうか?

Commandパターンは他にも、

  • QueueにCommandオブジェクトを登録して順次呼び出す
  • Commandオブジェクトを永続化して、必要な際に取り出して呼び出す
  • Commandオブジェクトを直列化(シリアライズ)して、ネットワークなどの境界を越えて実行

など、色々と活用する場面があるようです。

私も今後もどのように有効に活用できるか考えていきたいと思います。

 

昨年はまったくと言っていいほど進まなかった、このデザインパターンシリーズ。今年はがんばってやっていこうと思います。次回は「Adapterパターン」に入っていくつもりですので、今後ともお付き合いをお願いします。

なお、今後コードはC#のみとさせてもらいます。案外VBにportするのが大変なものでf(^^;

投稿日時 : 2010年1月7日 23:45

Feedback

No comments posted yet.
タイトル
名前
Url
コメント