pythonによるデザインパターン[Memento]
# Intro
この文章は結城 浩さんの「Java言語で学ぶデザインパターン入門」を、pythonで実装してみたサンプルコードです。
筆者の環境は以下の通りです。Python 3.6.3
まだ修行中の身なので間違いがあると思いますがご了承ください。 今回は「Memento」です。Mementoと聞くとクリストファー・ノーラン監督の映画を思い出してしまうのは、私だけじゃないはず…
# Summarise
本書のp270によるとMementoとは
インスタンスの状態を表す役割を導入して、カプセル化の崩壊(クラスの内部構造に依存したコード)に陥ることなく保存と復元を行う
とあります。例えばMementoパターンを用いると、プログラムに対して
- undo(やり直し)
- redo(再実行)
- history(作業履歴の作成)
- snapshot(現在状態の保存)
を行うことができます。
# Practice
## Specification
以下のような「フルーツを集めていくサイコロゲーム」を作成します。
- ゲームは自動的に進む
- 主人公はサイコロを振り、その目が次の状態を決定
- 1の目が出るとお金が増える
- 2の目が出るとお金が半分になる
- 6の目が出るとフルーツがもらえる
- お金がなくなったら終了
また、所持金が増えてきたらclass Memento
のインスタンスを作って、「現在の状態」(お金とフルーツ)を保存します。一方で、所持金が減ってきたら前に作っておいてclass Memento
のインスタンスを使って、「前の状態」(お金とフルーツ)に戻ります。
## Explanation
最初に状態を保存するclass Memento
を作成します。
お金とフルーツを保存するので、Fieldはint型のmoney
とList型のfruits
を持ちます。またお金を取得するメソッド def get_money
、フルーツを追加する def add_fruits
、フルーツを取得するdef get_fruits
を作成します。難しくないですね。
もしかしたらclass Memento
をわざわざ作らずに、後で作る class Gamer
(ゲームを示すクラス)の中にこの機能を入れ込めば良いと思うかもしれません。しかし、疎結合になることを考えるとclass Memento
を別に作成した方がいいです(後でまた出てきます)。
次にゲームを表すクラスclass Gamer
を作成します。
金を取得するメソッド def get_money
、フルーツを追加する def add_fruits
、フルーツを取得するdef get_fruits
(時々おいしい〇〇を取得できる)を実装し、ゲームのロジックが書いている def bet
を実装します。def bet
はすこし複雑ですが、ランダムにサイコロを振り、その目によって以下の処理を行うことを実装しているだけです。
- 1の目が出るとお金が増える
- 2の目が出るとお金が半分になる
- 6の目が出るとフルーツがもらえる
- それ以外は何も起きない
重要なのはclass Memento
を作成するdef create_memento
と、引数で渡されたclass Memento
から前の状態に戻る def restore_memento
です。ここでsnapshot(現在状態の保存)とundo(再実行)を実装しています。
class Gamer
の中にdef create_memento
と def restore_memento
があるということは、class Gamer
を介してのみ class Memento
を変更できます。逆にいうとclass Gamer
以外はclass Memento
を変更できません。
最後にmain関数を実装します(以下に示すのは簡略化したもの)。
class Gamer
を作成し、class Memento
も作成し、100回のループでゲームを実行します。前に保存したclass Memento
(前の状態)の所持金によっては、現在の状態を保存したり、前の状態から復帰したりします。
重要なのはmain関数からはclass Memento
を直接変更してはいないが、いつclass Memento
を作成・復帰するか決定している点です。これは先ほどのclass Gamer
とは対照的です。このように役割を分担することで疎結合になっています。役割分担をきちっとすることで、例えば
- 複数のステップのundoを行うように変更したい
- undoする時にも現在の状態をsnapshotするように変更したい
のような時でも、class Gamer
を変更することなく、ただmain関数を変更すればいいだけです。これが一番最初に言った、状態を保存する機能をclass Gamer
に入れ込まずに、別に作成したclass Memento
に実装した理由です。
## Sample Code
python3 memento.py==== 0
現状:[money = 100, fruits = []]
何も起こりませんでした
所持金は100円になりました==== 1
現状:[money = 100, fruits = []]
何も起こりませんでした
所持金は100円になりました....(以下略)
# Conclusion
オブジェクトの状態を保存するときは、別のクラス(今回はclass Memento
)を作成しそこに状態を保存します。そして、そのクラスを他のクラス(今回はclass Gamer
)から操作し、いつ保存・復帰するかをまた別の関数(今回はdef main
)で決定します。この構成によって、美しく設計できることを学びました😤
私の経験では、まだredo,undoなどの、オブジェクトの状態の保存を伴うようなプログラムを作成したことがないのですが、もし次出くわしたらこのパターンを使いたいと思います。
# ref
- 「増補改訂版 Java言語で学ぶデザインパターン入門」 結城浩(著)第11版
- 「メメント (映画) — Wikipedia」
https://ja.wikipedia.org/wiki/%E3%83%A1%E3%83%A1%E3%83%B3%E3%83%88_(%E6%98%A0%E7%94%BB)