9
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

デザインパターン勉強会 第18回:Mementoパターン

Last updated at Posted at 2018-01-23

はじめに

本エントリーは某社内で実施するデザインパターン勉強会向けの資料となります。
本エントリーで書籍「Java言語で学ぶデザインパターン入門」をベースに学習を進めますが、サンプルコードはC#に置き換えて解説します。

第1回:Iteratorパターン
第2回:Adapterパターン
第3回:Template Methodパターン
第4回:Factory Methodパターン
第5回:Singletonパターン
第6回:Prototypeパターン
第7回:Builderパターン
第8回:Abstract Factoryパターン
第9回:Bridgeパターン
第10回:Strategyパターン
第11回:Compositeパターン
第12回:Decoratorパターン
第13回:Visitorパターン
第14回:Chain of Responsibilityパターン
第15回:Facadeパターン
第16回:Mediatorパターン

Mementoパターンとは

memento
【名】
〔過去の体験・出来事などを思い出すために保管しておく小さな〕思い出の品
〔人を思い出すために保管しておく小さな〕形見
《Memento》《ローマカトリック》記憶

あるオブジェクトの、特定の時点での状態を記録し、必要に応じてその状態を復元するためのパターンです。
RPGで、死んじゃったからセーブした状態を復元するような機能をイメージすると理解し易いと思います。


サンプルプログラムのクラス図

Mementoパターンを使用したサンプルプログラムを紹介します。
memento.png


各クラスの役割

クラス名 役割
Gamer ゲームを行う主人公のクラス。Mementoのインスタンスを作る。
Memento Gamerの状態を表すクラス。
Program ゲームを進行させるクラス。Mementoのインスタンスを保存しておき、必要に応じてGamerの状態を復元する。

Gamerクラス

    class Gamer
    {
        private static readonly string[] _fruitNames = new string[]
        {
            "リンゴ",
            "ぶどう",
            "バナナ",
            "みかん"
        };
        private Random _randam = new Random();
        private IList<string> _fruits = new List<string>();
        
        public int Money { get; private set; }

        public Gamer(int money)
        {
            Money = money;
        }

        public void Bet()
        {
            int dice = _randam.Next(1, 6);
            switch (dice)
            {
                case 1:
                    Money += 100;
                    Console.WriteLine("所持金が増えました。");
                    break;
                case 2:
                    Money /= 2;
                    Console.WriteLine("所持金が半分になりました。");
                    break;
                case 6:
                    string fruit = GetFruit();
                    Console.WriteLine("フルーツをもらいました。");
                    _fruits.Add(fruit);
                    break;
                default:
                    Console.WriteLine("何も起こりませんでした。");
                    break;
            }
        }

        /// <summary>
        /// スナップショットをとる(メメントを作る)
        /// </summary>
        /// <returns></returns>
        public Memento CreateMemento()
        {
            Memento memento = new Memento(Money);
            // フルーツはおいしいものだけ保存する
            _fruits.Where(fruit => fruit.StartsWith("おいしい")).ToList().
                ForEach(deliciousFruit => memento.AddFruit(deliciousFruit)); 
            return memento;
        }

        /// <summary>
        /// アンドゥを行う(メメントからリストアする)
        /// </summary>
        /// <param name="memento"></param>
        public void RestoreMemento(Memento memento)
        {
            Money = memento.Money;
            _fruits = memento.Fruits;
        }

        public override string ToString()
        {
            var stringBuilder = new StringBuilder();
            foreach (var fruit in _fruits)
            {
                stringBuilder.Append(fruit + ",");
            }

            return $"[money = {Money}, fruits = {stringBuilder}]";
        }

        public string GetFruit()
        {
            string prefix = _randam.Next(2) == 1 ? "おいしい" : string.Empty;
            return prefix + _fruitNames[_randam.Next(_fruitNames.Length - 1)];
        }
    }


Mementoクラス

    class Memento
    {
        private IList<string> _fruits = new List<string>();
        public int Money { get; private set; }
        public IList<string> Fruits
        {
            get
            {
                return new List<string>(_fruits);
            }
        }

        public Memento(int money)
        {
            Money = money;
        }

        public void AddFruit(string fruit)
        {
            _fruits.Add(fruit);
        }
    }


Programクラス

    class Program
    {
        static void Main(string[] args)
        {
            var gamer = new Gamer(100); // 所持金100円でゲーム開始
            Memento memento = gamer.CreateMemento();
            for(int i = 0; i < 100; i++)
            {
                Console.WriteLine($"==== {i}");
                Console.WriteLine($"現状 {gamer}");

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

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

                // 保存/リストアの判断
                if (gamer.Money > memento.Money)
                {
                    Console.WriteLine($"    (だいぶ増えたので、現在の状態を保存しておこう)");
                    memento = gamer.CreateMemento();
                }
                else if (gamer.Money < memento.Money / 2)
                {
                    Console.WriteLine($"    (だいぶ減ったので、以前の状態に復帰しよう)");
                    gamer.RestoreMemento(memento);
                }

                Console.ReadLine();
            }
        }
    }


実行結果

実行結果は以下のようになります。

==== 0
現状 [money = 100, fruits = ]
何も起こりませんでした。
所持金は 100円になりました

==== 1
現状 [money = 100, fruits = ]
フルーツをもらいました。
所持金は 100円になりました

==== 2
現状 [money = 100, fruits = バナナ,]
フルーツをもらいました。
所持金は 100円になりました

==== 3
現状 [money = 100, fruits = バナナ,リンゴ,]
フルーツをもらいました。
所持金は 100円になりました

==== 4
現状 [money = 100, fruits = バナナ,リンゴ,バナナ,]
所持金が増えました。
所持金は 200円になりました
    (だいぶ増えたので、現在の状態を保存しておこう)

~~~~略~~~~

==== 8
現状 [money = 300, fruits = バナナ,リンゴ,バナナ,ぶどう,ぶどう,]
所持金が半分になりました。
所持金は 150円になりました

==== 9
現状 [money = 150, fruits = バナナ,リンゴ,バナナ,ぶどう,ぶどう,]
所持金が半分になりました。
所持金は 75円になりました
    (だいぶ減ったので、以前の状態に復帰しよう)

==== 10
現状 [money = 300, fruits = ]
フルーツをもらいました。
所持金は 300円になりました

~~~~略~~~~

==== 14
現状 [money = 300, fruits = おいしいバナナ,バナナ,]
所持金が増えました。
所持金は 400円になりました
    (だいぶ増えたので、現在の状態を保存しておこう)

~~~~略~~~~

==== 18
現状 [money = 300, fruits = おいしいバナナ,バナナ,]
所持金が半分になりました。
所持金は 150円になりました
    (だいぶ減ったので、以前の状態に復帰しよう)

==== 19
現状 [money = 400, fruits = おいしいバナナ,]
フルーツをもらいました。
所持金は 400円になりました


Mementoパターンのおさらい

  • GamerはMementoを作成し、また以前のMementoを渡されるとそれを元に自分を復元します。GamerはMementoの内部構造に強く依存しています。
  • Programは、Gamerのメソッドを利用してMementoを作成し、それを使ってGamerを復元することができます。しかし、ProgramはGamerやMementoの内部構造には依存していないため、GamerとMementoをセットで使うことにより、使う側から内部構造をカプセル化することが可能になります。
  • カプセル化の観点から、例で使用したMementoが持っているプロパティや操作は、本来はProgramに公開する必要がないため、Gamerにだけアクセスできるような設計になっていたほうが望ましいです。


サンプルコード

公開準備中です。


9
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?