2010年7月アーカイブ

今回の担当はMementoパターンです。

Mementoとは「記念品」、「形見」といった意味です。
Mementoパターンを利用すると以下が行えるようになります。
  • undo(やり直し)
  • redo(再実行)
  • history(作業履歴の作成)
  • snapshot(現在状態の保存)
オブジェクト指向でundoなどを実装するためにはインスタンスの持っている情報を
保存しておく必要があります。だたし、保存しておくだけではダメで保存しておいた
情報からインスタンスを元の状態に戻せないといけません。
インスタンスを復元するにはインスタンス内部の情報に自由にアクセスする
必要があります。しかし不用意にアクセスを許してしまうと、そのクラスの内部構造
に依存したコードを書いてしまいカプセル化を破壊する結果になってしまいます。

インスタンスの状態を表す役割を導入することにより上記のようなカプセル化の破壊
をすることなく保存と復元を行えるのが今回学ぶMementoパターンです。

クラス図
20090317213541.jpg

サンプルコード
ファイル分けてみた。

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を実装するなど

ギコ猫とMementoパターン



次の担当はFlyweightパターン。

同じものを共有して無駄をなくすパターンです。

どこの無駄を無くすのかというとメモリの使用量です。

インスタンスをできるだけ共有して無駄にnewしないことでインスタンスを
沢山作らせない→メモリの使用量を減らすということです。

クラス図
flyweight2.gif
サンプルコード

class BigChar {
    private $charName; // 文字の名前
    private $fontdata; // 大きな文字を表す文字列
    public function __construct($charName) {
        $this->charName = $charName;
        try {
            $fileName = "big" . $this->charName . ".txt";
            if (!file_exists($fileName)) {
                throw new Exception();
            }
            $this->fontdata = implode("", file($fileName));
        } catch (Exception $e) {
            $this->fontdata = $this->charName . "?";
        }
    }
    public function prints() {
        echo $this->fontdata;
    }
}
 
class BigCharFactory {
    private $pool = array();
    private static $singleton;
    private function __construct(){} // 外からnewさせない
    public static function getInstance() {
        if (!is_object(BigCharFactory::$singleton)) {
            BigCharFactory::$singleton =& new BigCharFactory();
        }
        return BigCharFactory::$singleton;
    }
    public function getBigChar($charName) {
        $bc = isset($this->pool[$charName]) 
                                 ? $this->pool[$charName] : null;
        if ($bc == null) {
            $bc = new BigChar($charName);
            $this->pool[$charName] = $bc;
        }
        return $bc;
    }
}
 
class BigString {
    private $bigChars = array();
    public function __construct($string) {
        $factory = BigCharFactory::getInstance();
        for ($i = 0; $i < strlen($string); $i++) {
            $this->bigChars[$i] = 
                           $factory->getBigChar(substr($string, $i, 1)); 
        }
    }
    public function prints() {
        for ($i = 0; $i < count($this->bigChars); $i++) {
            $this->bigChars[$i]->prints();
        }
    }
    	
}
 
if ($argc < 2) {
    echo "Usage: php flyweight.php digits\n";
    echo "Example: php flyweight.php 1212123\n";
    exit;
}
 
$bs = new BigString($argv[1]);
$bs->prints();

実行結果
$ php self_sample.php 123
......##........
..######........
......##........
......##........
......##........
......##........
..##########....
................
....######......
..##......##....
..........##....
......####......
....##..........
..##............
..##########....
................
....######......
..##......##....
..........##....
......####......
..........##....
..##......##....
....######......
................


利用者は Flyweight クラスにあたるインスタンスを取得する場合に、直接その

クラスのコンストラクタを呼び出す代わりに BigCharFactory#getBigChar()

にアクセスする。 一方、呼び出された BigCharFactory オブジェクトは、状況に

応じて振る舞いを変える。

その時点で対象のインスタンスが生成されていない場合
  1. 対象のインスタンスを新たに生成する。
  2. 生成したインスタンスをプールする(言い換えると、メンバのコンテナオブジェクトに格納する)。
  3. 生成されたインスタンスを返す。
対象のインスタンスが既に生成されていた場合
  1. 対象のインスタンスをプールから呼び出す。
  2. 対象のインスタンスを返す。

このように BigCharFactory では状況によって処理は違うけれど、利用者側が

得る結果は全く同じなので、利用者は BigCharFactory の内部構造を意識せず

に使うことが出来る。



メリット

オブジェクトを生成する処理をクライアント側から隠蔽することができます。
インスタンスを使いまわすのでメモリ消費量が抑えられる。
newの回数が減らせるのでプログラムのパフォーマンスもあげられる。

デメリット
Flyweight Objectはどこから呼ばれるかわからないので状態を持ってはいけない。
システム全体で、使いまわすインスタンスの把握が必要。
一度 FlyweightFactory に保存されたインスタンスは、たとえ不要になった場合でも
ガベージコレクションされることがないため、場合によっては明示的に FlyweightFactory
から削除する必要がある。

使いどころ

Flyweight パターンを採用すべき典型的な例は、不変なクラスを扱う場合。
不変なクラスとはインスタンスが生成された後にそのインスタンスの状態が変化しないようなクラス。
対象が不変でないクラスの場合は、その状態が変更されている場合に再利用できないのでこのパターンに適しません。


ギコ猫とFlyweightパターン




2012年1月

1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31        

ウェブページ

このアーカイブについて

このページには、2010年7月に書かれたブログ記事が新しい順に公開されています。

前のアーカイブは2010年6月です。

次のアーカイブは2010年8月です。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。

Powered by Movable Type 5.01