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


# はじめに  
本エントリーは某社内で実施するデザインパターン勉強会向けの資料となります。 
本エントリーで書籍「[Java言語で学ぶデザインパターン入門](http://www.hyuki.com/dp/)」をベースに学習を進めますが、サンプルコードはC#に置き換えて解説します。  
  
第1回：[Iteratorパターン](http://qiita.com/Nuits/items/800f080d44b52e1cae18)
第2回：[Adapterパターン](http://qiita.com/Nanakusajp/items/747d1913b1e8bea8d340)
第3回：[Template Methodパターン](http://qiita.com/_kyam/items/9b5722e6a9635af6e18b)
第4回：[Factory Methodパターン](https://qiita.com/ayayo/items/3eece04bbc4b7504179a)
第5回：[Singletonパターン](https://qiita.com/Keenag/items/cae53d55cb953562fee4)
第6回：[Prototypeパターン](https://qiita.com/aconit96/items/9235d30a07f508886c7e)
第7回：[Builderパターン](https://qiita.com/Uesk/items/8f12be304671e9905795)
第8回：[Abstract Factoryパターン](https://qiita.com/buru/items/bb3930dda1bdd2b5f70b)
第9回：[Bridgeパターン](https://qiita.com/mitakaosamu/items/cff9a39953ae03092f43)
第10回：[Strategyパターン](https://qiita.com/a1146234/items/fdecc374b7225d99ab07)
第11回：[Compositeパターン](https://qiita.com/Nanakusajp/items/32ed648eaadaa9910159)
第12回：[Decoratorパターン](https://qiita.com/_kyam/items/3072a70c1f31f690e00c#_reference-6ceee69f61eb05add1bd)
第13回：[Visitorパターン](https://qiita.com/ayayo/items/cf63041d047ea5b676c3#_reference-fd2b59d78f8524c2c305)
第14回：[Chain of Responsibilityパターン](https://qiita.com/Keenag/items/6ef9cd9671aca38833d8)
第15回：[Facadeパターン](https://qiita.com/aconit96/items/60c924dbc69a059e5746#_reference-9d763076e525fb232407)
第16回：[Mediatorパターン](https://qiita.com/Uesk/items/5c3f2b2efd1cb4bb17ab)


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

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

---

# サンプルプログラムのクラス図  
Mementoパターンを使用したサンプルプログラムを紹介します。
![memento.png](https://qiita-image-store.s3.amazonaws.com/0/202345/fc3bcc3f-01b2-8eef-117e-ce6512f7be02.png)
  
---

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

---

# Gamerクラス  

```csharp
    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クラス  

```csharp
    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クラス  

```csharp
    class Program
    {
        static void Main(string[] args)
        {
            var gamer = new Gamer(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にだけアクセスできるような設計になっていたほうが望ましいです。

---

---

# サンプルコード  

公開準備中です。

---