今回の担当はMementoパターンです。
Mementoとは「記念品」、「形見」といった意味です。
Mementoパターンを利用すると以下が行えるようになります。
保存しておく必要があります。だたし、保存しておくだけではダメで保存しておいた
情報からインスタンスを元の状態に戻せないといけません。
インスタンスを復元するにはインスタンス内部の情報に自由にアクセスする
必要があります。しかし不用意にアクセスを許してしまうと、そのクラスの内部構造
に依存したコードを書いてしまいカプセル化を破壊する結果になってしまいます。
インスタンスの状態を表す役割を導入することにより上記のようなカプセル化の破壊
をすることなく保存と復元を行えるのが今回学ぶMementoパターンです。
クラス図
サンプルコード
ファイル分けてみた。
Memento.php
Game.php
main.php
結果はこちら。(長いけどあえてさらす)
Mementoの実装解説
Mementoを作る作成者役(Originator)がGamerクラスです。
MementoクラスはOriginatorの内部情報をまとめます。
MementoはOriginatorの内部情報を持っていますが誰にでも
情報を見せるわけではなく、2種類のインターフェースを持ってます。
MainはCaretaker役といわれ現在のOriginator役の状態を保存したい
ときにそのことをOriginatorに伝えます。するとOriginatorはMementoを
つくりCaretakerに渡します。
Mementoの持つ2つのインターフェース
・wide interface
広い?インターフェースOriginatorだけに内部状態をさらけ出している
Mementoクラスの__construct
・narrow interface
狭い?インターフェースCaretaker役に対して参照させるだけ
MementoクラスのgetMoney
メリット
・undo、redo、history、snapshotなどの実装に使える
・MementoクラスがOriginator以外からはブラックボックスとなり情報の隠蔽ができる
カプセル化を破壊しない
・CaretakerとOriginatorを分けて役割分担を行うことでOriginatorを変更する必要がなくなる
※Caretakerがいつスナップショットを取るかを指示し保持も行う
OriginatorはMementoを作る役と復元するだけ
デメリット
・大量のMementoを保持するとメモリを大量に消費する(差分や圧縮して保存しないと)
・必要がなくなったときにちゃんとMementoを処理しないと残ったままになる
使いどころ
undo、redo、history、snapshot
Commandパターンとともに利用しundo、redoを実装するなど
Mementoとは「記念品」、「形見」といった意味です。
Mementoパターンを利用すると以下が行えるようになります。
- undo(やり直し)
- redo(再実行)
- history(作業履歴の作成)
- snapshot(現在状態の保存)
保存しておく必要があります。だたし、保存しておくだけではダメで保存しておいた
情報からインスタンスを元の状態に戻せないといけません。
インスタンスを復元するにはインスタンス内部の情報に自由にアクセスする
必要があります。しかし不用意にアクセスを許してしまうと、そのクラスの内部構造
に依存したコードを書いてしまいカプセル化を破壊する結果になってしまいます。
インスタンスの状態を表す役割を導入することにより上記のようなカプセル化の破壊
をすることなく保存と復元を行えるのが今回学ぶMementoパターンです。
クラス図
サンプルコード
ファイル分けてみた。
Memento.php
// Memento クラス class Memento { private $money; protected function __construct($money) { $this->money = $money; } public function getMoney() { return $this->money; } }
Game.php
// Game パッケージ require_once 'Memento.php'; class Gamer extends Memento { private $money; public function __construct( $money ) { $this->money = $money; } public function getMoney() { return $this->money; } public function bet() { $dice = mt_rand(1,6); // サイコロ振る echo "サイコロの目は" . $dice . "\n"; if( $dice == 1 ) { // 所持金が100増える $this->money += 100; echo "所持金が100増えました。\n"; } else if( $dice == 2 ) { // 所持金が100減る $this->money -= 100; echo "所持金が100減りました。\n"; } else if( $dice == 4 ) { // 所持金が200増える $this->money += 200; echo "所持金が200増えました。\n"; } else if( $dice == 6 ) { // 所持金が半分になる $this->money = $this->money / 2; echo "所持金が半分になりました。\n"; } else { echo "何も起こりませんでした。\n"; } } public function createMemento() { $mem = new Memento( $this->money ); return $mem; } public function restoreMemento( $mem ) { $this->money = $mem->getMoney(); } }
main.php
// main require_once 'Game.php'; $gamer = new Gamer( 500 ); $mem = $gamer->createMemento(); for( $i = 0; $i < 100; $i++ ) { echo "==============> " . $i . "\n"; $gamer->bet(); echo "所持金は" . $gamer->getMoney() . "円になりました。\n"; if( $gamer->getMoney() > $mem->getMoney() ) { echo "だいぶ増えたので現在の状態を保存しておこう。\n"; $mem = $gamer->createMemento(); } else if( $gamer->getMoney() < ( $mem->getMoney() / 2 ) ) { echo "だいぶ減ったので以前の状態に戻そう。\n\n"; $gamer->restoreMemento($mem); echo "状態復元! 所持金は" . $gamer->getMoney() . "円になりました。\n"; } }
結果はこちら。(長いけどあえてさらす)
$ php main.php ==============> 0 サイコロの目は6 所持金が半分になりました。 所持金は250円になりました。 ==============> 1 サイコロの目は6 所持金が半分になりました。 所持金は125円になりました。 だいぶ減ったので以前の状態に戻そう。 状態復元! 所持金は500円になりました。 ==============> 2 サイコロの目は5 何も起こりませんでした。 所持金は500円になりました。 ==============> 3 サイコロの目は3 何も起こりませんでした。 所持金は500円になりました。 ==============> 4 サイコロの目は6 所持金が半分になりました。 所持金は250円になりました。 ==============> 5 サイコロの目は2 所持金が100減りました。 所持金は150円になりました。 だいぶ減ったので以前の状態に戻そう。 状態復元! 所持金は500円になりました。 ==============> 6 サイコロの目は2 所持金が100減りました。 所持金は400円になりました。 ==============> 7 サイコロの目は6 所持金が半分になりました。 所持金は200円になりました。 だいぶ減ったので以前の状態に戻そう。 状態復元! 所持金は500円になりました。 ==============> 8 サイコロの目は1 所持金が100増えました。 所持金は600円になりました。 だいぶ増えたので現在の状態を保存しておこう。 ==============> 9 サイコロの目は2 所持金が100減りました。 所持金は500円になりました。 ==============> 10 サイコロの目は6 所持金が半分になりました。 所持金は250円になりました。 だいぶ減ったので以前の状態に戻そう。 状態復元! 所持金は600円になりました。 ==============> 11 サイコロの目は2 所持金が100減りました。 所持金は500円になりました。 ==============> 12 サイコロの目は6 所持金が半分になりました。 所持金は250円になりました。 だいぶ減ったので以前の状態に戻そう。 状態復元! 所持金は600円になりました。 ==============> 13 サイコロの目は5 何も起こりませんでした。 所持金は600円になりました。 ==============> 14 サイコロの目は1 所持金が100増えました。 所持金は700円になりました。 だいぶ増えたので現在の状態を保存しておこう。 ==============> 15 サイコロの目は6 所持金が半分になりました。 所持金は350円になりました。 ==============> 16 サイコロの目は1 所持金が100増えました。 所持金は450円になりました。 ==============> 17 サイコロの目は2 所持金が100減りました。 所持金は350円になりました。 ==============> 18 サイコロの目は6 所持金が半分になりました。 所持金は175円になりました。 だいぶ減ったので以前の状態に戻そう。 状態復元! 所持金は700円になりました。 ==============> 19 サイコロの目は4 所持金が200増えました。 所持金は900円になりました。 だいぶ増えたので現在の状態を保存しておこう。 ==============> 20 サイコロの目は1 所持金が100増えました。 所持金は1000円になりました。 だいぶ増えたので現在の状態を保存しておこう。 ==============> 21 サイコロの目は2 所持金が100減りました。 所持金は900円になりました。 ==============> 22 サイコロの目は2 所持金が100減りました。 所持金は800円になりました。 ==============> 23 サイコロの目は6 所持金が半分になりました。 所持金は400円になりました。 だいぶ減ったので以前の状態に戻そう。 状態復元! 所持金は1000円になりました。 ==============> 24 サイコロの目は6 所持金が半分になりました。 所持金は500円になりました。 ==============> 25 サイコロの目は6 所持金が半分になりました。 所持金は250円になりました。 だいぶ減ったので以前の状態に戻そう。 状態復元! 所持金は1000円になりました。 ==============> 26 サイコロの目は5 何も起こりませんでした。 所持金は1000円になりました。 ==============> 27 サイコロの目は6 所持金が半分になりました。 所持金は500円になりました。 ==============> 28 サイコロの目は2 所持金が100減りました。 所持金は400円になりました。 だいぶ減ったので以前の状態に戻そう。 状態復元! 所持金は1000円になりました。 ==============> 29 サイコロの目は6 所持金が半分になりました。 所持金は500円になりました。 ==============> 30 サイコロの目は5 何も起こりませんでした。 所持金は500円になりました。 ==============> 31 サイコロの目は2 所持金が100減りました。 所持金は400円になりました。 だいぶ減ったので以前の状態に戻そう。 状態復元! 所持金は1000円になりました。 ==============> 32 サイコロの目は3 何も起こりませんでした。 所持金は1000円になりました。 ==============> 33 サイコロの目は6 所持金が半分になりました。 所持金は500円になりました。 ==============> 34 サイコロの目は1 所持金が100増えました。 所持金は600円になりました。 ==============> 35 サイコロの目は6 所持金が半分になりました。 所持金は300円になりました。 だいぶ減ったので以前の状態に戻そう。 状態復元! 所持金は1000円になりました。 ==============> 36 サイコロの目は6 所持金が半分になりました。 所持金は500円になりました。 ==============> 37 サイコロの目は6 所持金が半分になりました。 所持金は250円になりました。 だいぶ減ったので以前の状態に戻そう。 状態復元! 所持金は1000円になりました。 ==============> 38 サイコロの目は4 所持金が200増えました。 所持金は1200円になりました。 だいぶ増えたので現在の状態を保存しておこう。 ==============> 39 サイコロの目は6 所持金が半分になりました。 所持金は600円になりました。 ==============> 40 サイコロの目は6 所持金が半分になりました。 所持金は300円になりました。 だいぶ減ったので以前の状態に戻そう。 状態復元! 所持金は1200円になりました。 ==============> 41 サイコロの目は1 所持金が100増えました。 所持金は1300円になりました。 だいぶ増えたので現在の状態を保存しておこう。 ==============> 42 サイコロの目は4 所持金が200増えました。 所持金は1500円になりました。 だいぶ増えたので現在の状態を保存しておこう。 ==============> 43 サイコロの目は6 所持金が半分になりました。 所持金は750円になりました。 ==============> 44 サイコロの目は4 所持金が200増えました。 所持金は950円になりました。 ==============> 45 サイコロの目は1 所持金が100増えました。 所持金は1050円になりました。 ==============> 46 サイコロの目は1 所持金が100増えました。 所持金は1150円になりました。 ==============> 47 サイコロの目は4 所持金が200増えました。 所持金は1350円になりました。 ==============> 48 サイコロの目は5 何も起こりませんでした。 所持金は1350円になりました。 ==============> 49 サイコロの目は2 所持金が100減りました。 所持金は1250円になりました。 ==============> 50 サイコロの目は2 所持金が100減りました。 所持金は1150円になりました。 ==============> 51 サイコロの目は1 所持金が100増えました。 所持金は1250円になりました。 ==============> 52 サイコロの目は4 所持金が200増えました。 所持金は1450円になりました。 ==============> 53 サイコロの目は4 所持金が200増えました。 所持金は1650円になりました。 だいぶ増えたので現在の状態を保存しておこう。 ==============> 54 サイコロの目は1 所持金が100増えました。 所持金は1750円になりました。 だいぶ増えたので現在の状態を保存しておこう。 ==============> 55 サイコロの目は2 所持金が100減りました。 所持金は1650円になりました。 ==============> 56 サイコロの目は3 何も起こりませんでした。 所持金は1650円になりました。 ==============> 57 サイコロの目は6 所持金が半分になりました。 所持金は825円になりました。 だいぶ減ったので以前の状態に戻そう。 状態復元! 所持金は1750円になりました。 ==============> 58 サイコロの目は5 何も起こりませんでした。 所持金は1750円になりました。 ==============> 59 サイコロの目は1 所持金が100増えました。 所持金は1850円になりました。 だいぶ増えたので現在の状態を保存しておこう。 ==============> 60 サイコロの目は3 何も起こりませんでした。 所持金は1850円になりました。 ==============> 61 サイコロの目は4 所持金が200増えました。 所持金は2050円になりました。 だいぶ増えたので現在の状態を保存しておこう。 ==============> 62 サイコロの目は6 所持金が半分になりました。 所持金は1025円になりました。 ==============> 63 サイコロの目は2 所持金が100減りました。 所持金は925円になりました。 だいぶ減ったので以前の状態に戻そう。 状態復元! 所持金は2050円になりました。 ==============> 64 サイコロの目は3 何も起こりませんでした。 所持金は2050円になりました。 ==============> 65 サイコロの目は3 何も起こりませんでした。 所持金は2050円になりました。 ==============> 66 サイコロの目は3 何も起こりませんでした。 所持金は2050円になりました。 ==============> 67 サイコロの目は1 所持金が100増えました。 所持金は2150円になりました。 だいぶ増えたので現在の状態を保存しておこう。 ==============> 68 サイコロの目は5 何も起こりませんでした。 所持金は2150円になりました。 ==============> 69 サイコロの目は5 何も起こりませんでした。 所持金は2150円になりました。 ==============> 70 サイコロの目は1 所持金が100増えました。 所持金は2250円になりました。 だいぶ増えたので現在の状態を保存しておこう。 ==============> 71 サイコロの目は5 何も起こりませんでした。 所持金は2250円になりました。 ==============> 72 サイコロの目は3 何も起こりませんでした。 所持金は2250円になりました。 ==============> 73 サイコロの目は6 所持金が半分になりました。 所持金は1125円になりました。 ==============> 74 サイコロの目は6 所持金が半分になりました。 所持金は562.5円になりました。 だいぶ減ったので以前の状態に戻そう。 状態復元! 所持金は2250円になりました。 ==============> 75 サイコロの目は3 何も起こりませんでした。 所持金は2250円になりました。 ==============> 76 サイコロの目は5 何も起こりませんでした。 所持金は2250円になりました。 ==============> 77 サイコロの目は5 何も起こりませんでした。 所持金は2250円になりました。 ==============> 78 サイコロの目は2 所持金が100減りました。 所持金は2150円になりました。 ==============> 79 サイコロの目は1 所持金が100増えました。 所持金は2250円になりました。 ==============> 80 サイコロの目は2 所持金が100減りました。 所持金は2150円になりました。 ==============> 81 サイコロの目は3 何も起こりませんでした。 所持金は2150円になりました。 ==============> 82 サイコロの目は3 何も起こりませんでした。 所持金は2150円になりました。 ==============> 83 サイコロの目は5 何も起こりませんでした。 所持金は2150円になりました。 ==============> 84 サイコロの目は2 所持金が100減りました。 所持金は2050円になりました。 ==============> 85 サイコロの目は2 所持金が100減りました。 所持金は1950円になりました。 ==============> 86 サイコロの目は4 所持金が200増えました。 所持金は2150円になりました。 ==============> 87 サイコロの目は1 所持金が100増えました。 所持金は2250円になりました。 ==============> 88 サイコロの目は2 所持金が100減りました。 所持金は2150円になりました。 ==============> 89 サイコロの目は1 所持金が100増えました。 所持金は2250円になりました。 ==============> 90 サイコロの目は2 所持金が100減りました。 所持金は2150円になりました。 ==============> 91 サイコロの目は4 所持金が200増えました。 所持金は2350円になりました。 だいぶ増えたので現在の状態を保存しておこう。 ==============> 92 サイコロの目は1 所持金が100増えました。 所持金は2450円になりました。 だいぶ増えたので現在の状態を保存しておこう。 ==============> 93 サイコロの目は5 何も起こりませんでした。 所持金は2450円になりました。 ==============> 94 サイコロの目は1 所持金が100増えました。 所持金は2550円になりました。 だいぶ増えたので現在の状態を保存しておこう。 ==============> 95 サイコロの目は2 所持金が100減りました。 所持金は2450円になりました。 ==============> 96 サイコロの目は1 所持金が100増えました。 所持金は2550円になりました。 ==============> 97 サイコロの目は4 所持金が200増えました。 所持金は2750円になりました。 だいぶ増えたので現在の状態を保存しておこう。 ==============> 98 サイコロの目は4 所持金が200増えました。 所持金は2950円になりました。 だいぶ増えたので現在の状態を保存しておこう。 ==============> 99 サイコロの目は4 所持金が200増えました。 所持金は3150円になりました。 だいぶ増えたので現在の状態を保存しておこう。
Mementoの実装解説
Mementoを作る作成者役(Originator)がGamerクラスです。
MementoクラスはOriginatorの内部情報をまとめます。
MementoはOriginatorの内部情報を持っていますが誰にでも
情報を見せるわけではなく、2種類のインターフェースを持ってます。
MainはCaretaker役といわれ現在のOriginator役の状態を保存したい
ときにそのことをOriginatorに伝えます。するとOriginatorはMementoを
つくりCaretakerに渡します。
Mementoの持つ2つのインターフェース
・wide interface
広い?インターフェースOriginatorだけに内部状態をさらけ出している
Mementoクラスの__construct
・narrow interface
狭い?インターフェースCaretaker役に対して参照させるだけ
MementoクラスのgetMoney
メリット
・undo、redo、history、snapshotなどの実装に使える
・MementoクラスがOriginator以外からはブラックボックスとなり情報の隠蔽ができる
カプセル化を破壊しない
・CaretakerとOriginatorを分けて役割分担を行うことでOriginatorを変更する必要がなくなる
※Caretakerがいつスナップショットを取るかを指示し保持も行う
OriginatorはMementoを作る役と復元するだけ
デメリット
・大量のMementoを保持するとメモリを大量に消費する(差分や圧縮して保存しないと)
・必要がなくなったときにちゃんとMementoを処理しないと残ったままになる
使いどころ
undo、redo、history、snapshot
Commandパターンとともに利用しundo、redoを実装するなど