Pythonで、デザインパターン「Memento」を学ぶ

GoFのデザインパターンを学習する素材として、書籍「[増補改訂版Java言語で学ぶデザインパターン入門](https://www.hyuki.com/dp/)」が参考になるみたいですね。ただ、取り上げられている実例は、JAVAベースのため、自分の理解を深めるためにも、Pythonで同等のプラクティスに挑んでみました。

# ■ Memento（メメント・パターン）
「Memento」という英単語は、「形見・記念」を意味します。
このパターンは、あるオブジェクトの任意の時点の状態を覚えておき(保存)、 後でその状態にオブジェクトを戻すための工夫を提供するパターンです。(カプセル化を破壊せずに、状態を元に戻せる)つまり、テキストエディタ等で実装されているような「アンドゥ」(操作をキャンセルして操作前の状態に戻す)機能を提供するためのパターンです。
注意すべきことは状態を元に戻すための必要最小限の情報(フィールド値)のみを保存すると言うことです。
[(以上、「ITエンジニアのための技術支援サイト by IT専科」より引用)](http://www.itsenka.com/contents/development/designpattern/memento.html)

## UML class and sequence diagram
![W3sDesign_Memento_Design_Pattern_UML.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103595/ecf574bf-9d16-2d33-b57d-92f1dfdd8aaf.jpeg)
[(以上、ウィキペディア（Wikipedia）より引用)](https://en.wikipedia.org/wiki/Memento_pattern)

# ■ "Memento"のサンプルプログラム
実際に、Mementoパターンを活用したサンプルプログラムを動かしてみて、次のような動作の様子を確認したいと思います。なお、サンプルプログラム**「フルーツを集めていくサイコロゲーム」**は、次のような動作を想定するものとします。

- このゲームは自動的に進みます
- ゲームの主人公は、サイコロを振り、サイコロの目に応じて動作が決定します
- ゲームの都度、現在の状況を表示します（所持金、所持しているフルーツ）
- ゲームの開始時点では、所持金100円からスタート
- **現時点の所持金が、保存しておいた所持金を上回った場合は、その状況（所持金と所持している"おいしいフルーツ"）を保存します**
- **現時点の所持金が、保存しておいた所持金の半分を下回った場合は、以前に保存したその状況（所持金、所持している"おいしいフルーツ"）を現在の状況として復元します**
- お金がなくなったら終了します。
- 最大100回、ゲームを繰り返します


＜サイコロの目に応じた動作＞
1. サイコロの目が<font color="blue">"1"</font>が出たとき、所持金が**100円**増えます
2. サイコロの目が<font color="red">"2"</font>が出たとき、所持金が**半分**になります（端数は、切り捨て）
3. サイコロの目が<font color="blue">"6"</font>が出たとき、**フルーツ**が貰えます
　（普通の**"フルーツ"**が貰えるが、**"おいしいフルーツ"**が貰えるか、確率は、50%です）
4. その他のサイコロの目が出た場合は、何も起こりません

```console
$ python Main.py 
==== 0
現状:[money = 100, fruits = []]
所持金が増えました
所持金は200円になりました
      (だいぶ増えたので、現在の状態を保存しておこう)

==== 1
現状:[money = 200, fruits = []]
フルーツ(おいしいぶどう)をもらいました
所持金は200円になりました

==== 2
現状:[money = 200, fruits = ['おいしいぶどう']]
何も起こりませんでした
所持金は200円になりました

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


...(snip)


==== 22
現状:[money = 500, fruits = ['おいしいぶどう', 'おいしいぶどう', 'おいしいバナナ', 'おいしいリンゴ', 'リンゴ', 'バナナ']]
フルーツ(おいしいバナナ)をもらいました
所持金は500円になりました

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

==== 24
現状:[money = 600, fruits = ['おいしいぶどう', 'おいしいぶどう', 'おいしいバナナ', 'おいしいリンゴ', 'リンゴ', 'バナナ', 'おいしいバナナ']]
所持金が半分になりました
所持金は300円になりました

==== 25
現状:[money = 300, fruits = ['おいしいぶどう', 'おいしいぶどう', 'おいしいバナナ', 'おいしいリンゴ', 'リンゴ', 'バナナ', 'おいしいバナナ']]
何も起こりませんでした
所持金は300円になりました

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

==== 27
現状:[money = 600, fruits = ['おいしいぶどう', 'おいしいぶどう', 'おいしいバナナ', 'おいしいリンゴ', 'おいしいバナナ']]
所持金が半分になりました
所持金は300円になりました

...(snip)
```
最後の方で、`Memento`パターンを使った動作が確認できました。

# ■ サンプルプログラムの詳細
Gitリポジトリにも、同様のコードをアップしています。
https://github.com/ttsubo/study_of_design_pattern/tree/master/Memento

- ディレクトリ構成

```console
.
├── Main.py
└── memento
    ├── __init__.py
    ├── gamer.py
    └── memento.py
```
## (1) Originator（作成者）の役
`Originator`役は、自分の現在の状態を保存したいときに、`Memento`役を作ります。`Originator`役はまた、以前の`Memento`役を渡されると、その`Memento`役を作った時点の状態に戻る処理を行います。
サンプルプログラムでは、`Gamer`クラスが、この役を努めます。

```python:memento/gamer.py
import random
from memento.memento import Memento

class Gamer(object):
    def __init__(self, money):
        self.__fruitname = ["リンゴ", "ぶどう", "バナナ", "みかん"]
        self.__money = money
        self.__fruits = []

    def getMoney(self):
        return self.__money

    def bet(self):
        dice = random.randint(1, 6)
        if dice == 1:
            self.__money += 100
            print("所持金が増えました")
        elif dice == 2:
            self.__money //= 2
            print("所持金が半分になりました")
        elif dice == 6:
            f = self.__getFruit()
            print("フルーツ({0})をもらいました".format(f))
            self.__fruits.append(f)
        else:
            print("何も起こりませんでした")

    def createMemento(self):
        m = Memento(self.__money)
        for f in self.__fruits:
            if f.startswith("おいしい"):
                m.addFruit(f)
        return m

    def restoreMemento(self, memento):
        self.__money = memento.money
        self.__fruits = memento.getFruits()

    def __str__(self):
        return "[money = {0}, fruits = {1}]".format(self.__money, self.__fruits)

    def __getFruit(self):
        prefix = ''
        if bool(random.getrandbits(1)):
            prefix = "おいしい"
        return prefix + random.choice(self.__fruitname)
```
## (2) Memento（記念品）の役
`Memento`役は、`Originator`役の内部情報をまとめます。`Memento`役は、`Originator`役の内部情報を持っていますが、その情報を誰にでも公開するわけではありません。
サンプルプログラムでは、`Memento`クラスが、この役を努めます。

```python:memento/memento.py
class Memento(object):
    def __init__(self, money):
        self.money = money
        self.fruits = []

    def getMoney(self):
        return self.money

    def addFruit(self, fruit):
        self.fruits.append(fruit)

    def getFruits(self):
        return self.fruits
```
## (3) Caretaker（世話をする人）の役
`Caretaker`役は、現在の`Originator`役の状態を保存したいときに、そのことを`Originator`役に伝えます。`Originator`役は、それを受けて`Memento`役を作り、`Caretaker`役に渡します。
`Caretaker`役は将来の必要に備えて、その`Memento`役を保存しておきます。
サンプルプログラムでは、`startMain`メソッドが、この役を努めます。

```python:Main.py
import time
from memento.gamer import Gamer

def startMain():
    gamer = Gamer(100)
    memento = gamer.createMemento()

    for i in range(100):
        print("==== {0}".format(i))
        print("現状:{0}".format(gamer))
        gamer.bet()
        print("所持金は{0}円になりました".format(gamer.getMoney()))

        if gamer.getMoney() > memento.getMoney():
            print("      (だいぶ増えたので、現在の状態を保存しておこう)")
            memento = gamer.createMemento()
        elif gamer.getMoney() < memento.getMoney() / 2:
            print("      (だいぶ減ったので、以前の状態に復帰しよう)")
            gamer.restoreMemento(memento)

        time.sleep(1)
        print("")

if __name__ == '__main__':
    startMain()
```

# ■ 参考URL
- [「Java言語で学ぶデザインパターン入門」を終えて(ない)](https://medium.com/since-i-want-to-start-blog-that-looks-like-men-do/java言語で学ぶデザインパターン入門-を終えて-ない-2cc9b34a30b2)