C#デザインパターン  - Memento

ここに掲載したコードは、『増補改訂版Java言語で学ぶデザインパターン入門 / 結城 浩(著)』に掲載されているサンプルコードをC#に移植したものです。
解説らしい解説は載せていませんので、『増補改訂版Java言語で学ぶデザインパターン入門 / 結城 浩(著)』も合わせてお読みください。

※当ソースは、Visual C# 2008 Express Editionで動作を確認しています。
  Visual Studio 2005をお使いの方は、ブログの記事を残しておきますので、そちらをご覧ください。



■Memento.cs
using System.Collections.Generic;

namespace Gushwell.DesignPatterns.Game {

    public class Memento  {
        private int money; // 所持金
        private ICollection<string> fruits; // フルーツ

        // コンストラクタ(wide interface)
        public Memento(int money) {
            this.money = money;
            this.fruits = new List<string>();
        }

        // 所持金を得る(narrow interface)
        public virtual int Money {
            get { return money; }
        }

        // フルーツを得る(wide interface)
        public virtual ICollection<string> CloneFruits {
            get { return new List<string>(fruits); }
        }

        // フルーツを追加する(wide interface)
        public virtual void AddFruit(string fruit) {
            fruits.Add(fruit);
        }
    }
}


■Gamer.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Gushwell.Dejavu;

namespace Gushwell.DesignPatterns.Game {

    public class Gamer {
        private int money; // 所持金
        private ICollection<string> fruits = new List<string>(); // フルーツ
        private Random random = new Random(); // 乱数発生器
        private static string[] fruitsname = new string[] { "リンゴ", "ぶどう", "バナナ", "みかん" };

        // コンストラクタ
        public Gamer(int money) {
            this.money = money;
        }

        // 現在の所持金を得る
        public virtual int Money {
            get { return money; }
        }

        // フルーツを1個得る
        private string GetFruit() {
            string prefix = "";
            if (random.Next(0, 2) == 0) {
                prefix = "おいしい";
            }
            return prefix + fruitsname[random.Next(fruitsname.Length)];
        }

        public virtual void Bet() {
            // 賭ける…ゲームの進行
            int dice = random.Next(6) + 1; // サイコロを振る
            if (dice == 1) {
                // 1の目…所持金が増える
                money += 100;
                Console.WriteLine("所持金が増えました。");
            } else if (dice == 2) {
                // 2の目…所持金が半分になる
                money /= 2;
                Console.WriteLine("所持金が半分になりました。");
            } else if (dice == 6) {
                // 6の目…フルーツをもらう
                string f = GetFruit();
                Console.WriteLine("フルーツ({0})をもらいました。",f);
                fruits.Add(f);
            } else {
                // それ以外…何も起きない
                Console.WriteLine("何も起こりませんでした。");
            }
        }

        // スナップショットをとる
        public virtual Memento CreateMemento() {
            Memento m = new Memento(money);
            // フルーツはおいしいものだけ保存
            fruits.Where(f => f.StartsWith("おいしい"))
                  .ForEach(f => m.AddFruit(f));
            return m;
        }

        // アンドゥを行う
        public virtual void RestoreMemento(Memento memento) {
            this.money = memento.Money;
            this.fruits = memento.CloneFruits;
        }

        // 文字列表現
        public override string ToString() {
            string s = string.Join(", ", fruits.ToArray());
            return string.Format("[money = {0}, fruits = [{1}]]", money, s);
        }
    }
}



■Program.cs
using System;
using System.Threading;
using Gushwell.DesignPatterns.Game;
using Gushwell.Dejavu;

namespace Gushwell.DesignPatterns {
    public class Program {
        public static void Main(string[] args) {
            Gamer gamer = new Gamer(100);            // 最初の所持金は100
            Memento memento = gamer.CreateMemento(); // 最初の状態を保存しておく

            100.Times(i => {
                Console.WriteLine("==== {0}",i);      // 回数表示
                Console.WriteLine("現状:{0}",gamer);  // 現在の主人公の状態表示

                gamer.Bet(); // ゲームを進める

                Console.WriteLine("所持金は {0}円になりました。", gamer.Money);

                // Mementoの取り扱いの決定
                if (gamer.Money > memento.Money) {
                    Console.WriteLine("    (だいぶ増えたので、現在の状態を保存しておこう)");
                    memento = gamer.CreateMemento();
                } else if (gamer.Money < memento.Money / 2) {
                    Console.WriteLine("    (だいぶ減ったので、以前の状態に復帰しよう)");
                    gamer.RestoreMemento(memento);
                }

                // 時間待ち
                Thread.Sleep(1000);
                Console.WriteLine("");
            });
        }
    }
}



■nTimes.cs
using System;

namespace Gushwell.Dejavu {
    public static class nTimes {
        public static void Times(this int n, Action<int> action) {
            for (int i = 0; i < n; i++) {
                action(i);
            }
        }
    }
}



■EnumerableExtentions.cs
using System;
using System.Collections.Generic;

namespace Gushwell.Dejavu {
    public static class EnumerableExtentions {
        public static void ForEach<T>(this IEnumerable<T> source, Action<T> action) {
            foreach (var x in source) {
                action(x);
            }
        }
    }
}