プログラムの最近のブログ記事

Advent Calendar 2010 まとめ

| コメント(1) | トラックバック(0)
社内勉強会用Observerパターン。

observerとは「観察者」という意味。

Observerパターンは観察対象の状態が変化すると、観察者に
対して変化が通知される。


状態変化に応じた処理を記述するときに有効なパターン。


Observerパターンのクラス図

observer.gif


Observerパターンのサンプルプログラム

今回のサンプルプログラムは結城本のサンプルをPHPにした感じです。
数を生成するオブジェクトを観察者が観察して、生成された数字を観察者
毎に異なる形式で表示します。
DigitObserverは数字で表示します。
GraphObserverは「*」の棒グラフで表示します。

・Observer側(Observer.php)
// interface Observer
interface Observer {
  public function update($generator);
  public function getName();
}


// DigitObserver Class
// 数字で表示します。
class DigitObserver implements Observer {
  public function update($generator) {
    echo "DigitObserver:" . $generator->getNumber() . "\n";
  }
  public function getName() {
    return 'DigitObserver';
  }
}



// GraphObserver Class
// 「*」で棒グラフみたいに出力します。
class GraphObserver implements Observer {
  public function update( $generator ) {
    echo " GraphObserver: ";
    for( $i = 0; $i < $generator->getNumber(); $i++ ) {
      echo "*";
    }
    echo "\n";
  }
  public function getName() {
    return 'GraphObserver';
  }
}


・Subject側(Generator.php)
// Abstract NumberGenerator
// 抽象クラス
abstract class NumberGenerator {
  private $observers = array();

  public function addObserver( $observer ) {
    $this->observers[ $observer->getName() ] = $observer;
  }

  public function deleteObserver( $observer ) {
    unset( $this->observers[ $observer->getName() ] );
  }

  public function notifyObservers() {
    foreach( $this->observers as $val ) {
      $val->update( $this );
    }
  }

  abstract public function getNumber();
  abstract public function execute();
}


// RandomNumberGenerator Class
// 具象クラス
class RandomNumberGenerator extends NumberGenerator {
  private $number;
  public function __construct() {
    srand(time());
  }
  public function getNumber() {
    return $this->number;
  }
  public function  execute() {
    for ($i = 0; $i < 20; $i++) {
      $this->number = rand(0, 49);
      $this->notifyObservers();
    }
  }
}

※Iteratorパターンは使ってません・・・。


・mainプログラム(main.php)
require "Observer.php";
require "Generator.php";

// 具象クラス
$generator = new RandomNumberGenerator();

// 観察者1
$observer1 = new DigitObserver();

// 観察者2
$observer2 = new GraphObserver();

// 観察対象に観察者1を追加
$generator->addObserver($observer1);

// 観察対象に観察者2を追加
$generator->addObserver($observer2);

// 実行
$generator->execute();

実行結果
$ php main.php
DigitObserver:34
 GraphObserver: **********************************
DigitObserver:1
 GraphObserver: *
DigitObserver:14
 GraphObserver: **************
DigitObserver:9
 GraphObserver: *********
DigitObserver:24
 GraphObserver: ************************
DigitObserver:36
 GraphObserver: ************************************
DigitObserver:18
 GraphObserver: ******************
DigitObserver:42
 GraphObserver: ******************************************
DigitObserver:12
 GraphObserver: ************
DigitObserver:36
 GraphObserver: ************************************
DigitObserver:35
 GraphObserver: ***********************************
DigitObserver:8
 GraphObserver: ********
DigitObserver:13
 GraphObserver: *************
DigitObserver:22
 GraphObserver: **********************
DigitObserver:6
 GraphObserver: ******
DigitObserver:36
 GraphObserver: ************************************
DigitObserver:20
 GraphObserver: ********************
DigitObserver:39
 GraphObserver: ***************************************
DigitObserver:33
 GraphObserver: *********************************
DigitObserver:40
 GraphObserver: ****************************************

解説

Observerインターフェースは「観察者」を表現するインターフェースで具体的な
観察者はこのインターフェースを実装します。
NumberGeneratorがupdateメソッドを呼び出します。
updateメソッドはNumberGeneratorが内容の変更をObserverに通知するための
メソッドです。
NumberGeneratorは数を生成する抽象クラスです。
実際の数の生成と数を取得する部分はサブクラスに実装してほしいので
抽象メソッドになってます。
observersプロパティはNumGeneratorを観察しているObserverたちを保存
しています。
addObserverはObserverを追加すメソッド、deleteObserverはObserverを削除
するメソッドです。
notifyObserversメソッドはObserver全員に更新を伝えるメソッドです。
※addObserverでobserversプロパティに登録されているObserver達に通知。


Observerパターンでの役割分担
・Subject(被験者)役
  サンプルプログラムでのNumberGeneratorクラスに相当
・ConcreteSubject(具体的な被験者)役
  サンプルプログラムでのRandomNumberGeneratorクラスに相当
・Observer(観察者)役
  サンプルプログラムでのObserverインターフェースに相当
・ConcreteObserver(具体的な観察者)役
  サンプルプログラムでのDigitObserverクラスやGraphObserverクラスに相当

 
メリット
・具体的な被験者クラスは現在自分を観察しているクラスについて気にする必要が無い。
・具体的な観察者クラスも自分の観察しているについて気にする必要が無い。
・通知したい観察者だけを被験者が登録すればいい。
・観察対象がイベントの監視を一括して行えるように監視義務を委譲できる。
・将来新たにObsesrverを追加する必要が出てきても、Subjectを変更する必要が無い。

デメリット
・updateメソッドが呼ばれる順番が変わっても問題が起きない実装にしなければならない
 ※Observerの行為がSubjectに影響を与える場合は互いに呼び出しが続く可能性がある
   ⇒Observer役に「現在Subject役から通知されている最中がどうか」をあらわすフラグを持たせるなどで回避


実装する時の順番
1.Observerの振る舞いを統一する
2.オブザーバに自らを登録させる
3.イベント発生の際、オブザーバに通知する
4.観察対象から追加情報を取得する

Observerパターンって名前がなんか・・・
Observerパターンの解説を読んでいつも気持ち悪いなと思うところ。。
「観察者」と説明しているのに観察対象者側がどの観察者に観察させるかを
選んでいるところ。
観察というよりは変更を教えてもらってる感が非常に強い。
ObserverパターンのObserverは能動的ではなくて受動的。

で、どうもObserverパターンをいう呼び名以外にPublish-Subscribeパターン
と呼ばれることもあるらしい。「Publish」発行と「subscribe」購読ということみたい。
こっちの呼び名のほうがしっくりくると思うんだけど、みんなはどうなんだろ?

MVCとか
Model/View/ControllerのModelとViewの関係はObserverパターンの
Subject役とObserver役の関係に対応しているみたい。
ViewがModelをどのようにみせるか?という役割をもっているのであれば
Modelが変わったんだからViewも変わるはずという意味では納得。


ギコ猫とObserverパターン




今回の担当は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パターン




現在社内勉強会ではデザインパターンが持ち回りで行われています。

とりあえず今回は社内勉強用資料として。

自分の担当はAbstractFactoryパターン。

AbstractFactory = 「抽象的な工場」・・・。

名前だけでは一体何がうれしいのかまったくわかりませんね。

デザパタ本での解説は関連する部品を組み合わせて製品を作るとなっています。

その他では・・・

インスタンスの生成を専門に行うクラスを用意することで、
整合性を必要とされる一連のオブジェクト群を間違いなく生成するパターン

「関連、または依存しあうオブジェクトのファミリを、その具象クラスを指定すること無しに生成する
インターフェースを提供する」

とか言われています。

イメージ図はこんな感じ。

abstract_factory.gif

で、クラス図で表すと下のようになります。

Untitled.png
まぁ何はともあれAbstractFactoryを使ったサンプルプログラムを見てみましょう。。
サンプルなので小さめに。
// ペットボトル工場の抽象クラス
abstract class abstractPlasticBottleFactory {
  abstract public function makeContent();
  abstract public function makeBottle();
}

// オレンジ工場(具象クラス)
class orangeFactory extends abstractPlasticBottleFactory {

  public function makeContent() {
    return new orangeContent();
  }

  public function makeBottle() {
    return new orangeBottle();
  }

}

// コーラ工場(具象クラス)
class colaFactory extends abstractPlasticBottleFactory {

  public function makeContent() {
    return new colaContent();
  }

  public function makeBottle() {
    return new colaBottle();
  }

}

// 中身の抽象クラス
abstract class Content {
  abstract public function createContent();
}

class orangeContent extends Content {

  public function createContent() {
    return 'Create Orange Juice';
  }

}

class colaContent extends Content {

  public function createContent() {
    return 'Create Cola';
  }

}

// ボトルの抽象クラス
abstract class bottle {
  abstract public function createBottle();
}

class orangeBottle extends bottle {

  public function createBottle() {
    return 'Create Orange Bottle';
  }

}

class colaBottle extends bottle {

  public function createBottle() {
    return 'Create Cola Bottle';
  }

}

// クライアントクラス
class createFactory {
  private $content=null;
  private $bottle=null;

  function __construct( abstractPlasticBottleFactory $factory ) {
    $this->content = $factory->makeContent();
    $this->bottle = $factory->makeBottle();
  }

  public function create() {
    echo $this->content->createContent() . "\n";
    echo $this->bottle->createBottle() . "\n";
  }
}

// オレンジジュース
$orange_factory = new createFactory( new orangeFactory );
$orange_factory->create();

// コーラ
$cola_factory = new createFactory( new colaFactory );
$cola_factory->create();



実行すると・・・。
Create Orange Juice
Create Orange Bottle
Create Cola
Create Cola Bottle
サンプルの例がなんかおかしい気もしますが。。

今回のサンプルでは「abstractPlasticBottleFactory」が工場の抽象クラスとなり、
「abstractPlasticBottleFactory」を継承する工場の具象クラスの実装すべきメソッドを定義しています。

工場の具象クラスでは利用すべきオブジェクト(材料)を用意し独自に実装を行えます。
工場の具象クラスで利用する各材料に関しても抽象クラスを定義して
おくことによりクライアントプログラム対してどの工場を利用するのかだけ指定すれば
一連のオブジェクト群を間違いなく生成することができます。

今回の例は「Orange」と「Cola」の2つだけですが、具象クラスを同様に増やすことで
工場のバリエーションを増やすことができます。

利用する側はどんな工場が存在するのかさえ知っていればどの工場でも
同じ呼び出し方法で利用可能です。
(※工場内で何が起きているかは知らなくても使える)

メリット
・具象クラスに処理をまとめることができる
・利用する材料の組み合わせを各工場で変更できる
・クライアントクラスが再利用可能

デメリット
・抽象クラスの工場で新たにメソッドを定義した場合に抽象クラスの工場を継承する全ての具象工場で
変更が必要になる。

要は、インスタンス化を直接行わず、工場となる関数にお任せすることです。
こうすることにより、部品となる クラスのインスタンス化が、ソースコードの中に
散在しなくなるため、変更が容易になります。
一度工場を作ってしまえ ば、後は全ての操作がインタフェースクラス
経由で行われる訳です。
このように、全てがインタフェースクラス経由でだけ行われるため、外部から
は、内部で何をしているかは 一切気にかける必要はなく、しかも、結果的に
どんな部品が作られようとも、同じ操作ができます。
また、1つの工場で作成される部品のセットは、常に同じものなので、複数の部品を
常に正しい組み合わせ で生成できます。
Abstract Factoryパターンでは、このような間違った組み合わせが起こり得ません。 
部品を作るのは工場の内部であり、外部からはその工場を直接触らない
(=インタフェースしか触れない)ためです。

で、↑のデメリットに対して抽象クラスの工場でデフォルトの指定をしておけばいいじゃないか?
という意見もあります。。
確かにそうすれば全ての具象クラスで処理を追加する必要も無くなる。
でもそれって工場の種類が増えた場合に、具象クラスでメソッドの実装を忘れたら
意図しない結果が返ってくる可能性もあるのではなかろうか?
材料の組み合わせを各具象クラスが責任を持って指定していないのだから。。
そのときの要件によってどっちがいいのか変わるようなきがする・・・。


ギコ猫とAbstract Factoryパターン













スタートガイドのプログラムを少しいじってみる。

なんとなく電話のメモを取るような感じにしてみる。(ほぼ掲示板だけど・・・。)

app.yaml

application: memo
version: 1
runtime: python
api_version: 1

handlers:
- url: .*
  script: main.py

main.py

import cgi

from google.appengine.api import users
from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app
from google.appengine.ext import db
import os
from google.appengine.ext.webapp import template

class Greeting(db.Model):
  author = db.UserProperty()
  content = db.StringProperty(multiline=True)
  company_name = db.StringProperty()
  client_name = db.StringProperty()
  rank = db.StringProperty()
  date = db.DateTimeProperty(auto_now_add=True)


class MainPage(webapp.RequestHandler):
  def get(self):
    greetings_query = Greeting.all().order('-date')
    greetings = greetings_query.fetch(10)

    if users.get_current_user():
      url = users.create_logout_url(self.request.uri)
      url_linktext = 'Logout'
    else:
      url = users.create_login_url(self.request.uri)
      url_linktext = 'Login'

    template_values = {
      'greetings': greetings,
      'url': url,
      'url_linktext': url_linktext,
      }

    path = os.path.join(os.path.dirname(__file__), 'index.html')
    self.response.out.write(template.render(path, template_values))

class Guestbook(webapp.RequestHandler):
  def post(self):
    greeting = Greeting()

    if users.get_current_user():
      greeting.author = users.get_current_user()

    greeting.content = self.request.get('content')
    greeting.company_name = self.request.get('company_name')
    greeting.client_name = self.request.get('client_name')
    greeting.rank = self.request.get('rank')
    greeting.put()
    self.redirect('/')

application = webapp.WSGIApplication(
                                     [('/', MainPage),
                                      ('/sign', Guestbook)],
                                     debug=True)

def main():
  run_wsgi_app(application)

if __name__ == "__main__":
  main()
index.html


<html>
  <body>
    {% for greeting in greetings %}
      {% if greeting.author %}
        <b>{{ greeting.author.nickname }}</b> wrote:
      {% else %}
       An anonymous person wrote:
      {% endif %}
      <p>会社:{{ greeting.company_name|escape }} | 担当者:{{ greeting.client_name|escape }} | 手ごたえ:{{ greeting.rank|escape }}%</p>
      <blockquote>{{ greeting.content|escape }}</blockquote>
    {% endfor %}
<hr />
    <form action="/sign" method="post">
      <div>会社:<input type="text" name="company_name">  担当者:<input type="text" name="client_name"></div>
      <div>手ごたえ:<select name="rank">
      	<option value="0" />0
      	<option value="10" />10
      	<option value="20" />20
      	<option value="30" />30
      	<option value="40" />40
      	<option value="50" />50
      	<option value="60" />60
      	<option value="70" />70
      	<option value="80" />80
      	<option value="90" />90
      	<option value="100" />100
      </select>%</div>
      <div>メモ:<br /><textarea name="content" rows="3" cols="60"></textarea></div>
      <div><input type="submit" value="Sign Guestbook"></div>
    </form>

    <a href="{{ url }}">{{ url_linktext }}</a>

  </body>
</html>


sample_1.jpg

何とか動いた。
main.pyの「Greeting」class(サンプルのまま名前変えてない・・・。)でモデルの定義をしてる。
どのデータがどのProperty クラスなのかわからなくなったら型とプロパティ クラスを参照。

ほんの少しつかめてきた。

Google App Engine 実践リファレンス
清野 克行
技術評論社
売り上げランキング: 47717
おすすめ度の平均: 4.0
4 これから始めたい方に・・・JavaでもPythonでも
GAE Pythonの環境ができたので、Googleの公式にあるスタートガイドどおりにやってみる。

スタート ガイド: Python

  1. 概要
  2. 開発環境
  3. Hello, World!
  4. webapp フレームワークの使用
  5. ユーザー サービスの使用
  6. webapp を使ったフォームの操作
  7. データストアの使用
  8. テンプレートの使用
  9. 静的ファイルの使用
  10. アプリケーションのアップロード

基本的には書いてあるとおりにやれば問題なく動いた。
でもWindows7 上で実験してる際にデータストアの使用のところで躓いた。
ローカルで動かそうとしたときにデータストアにアクセスできないようなエラーが出た。
そもそもデータストアの場所がローカルのどこにあるのかわからなかったので
ググってみたら以下のエントリを発見

App EngineでPythonを覚えるブログ

同じようにdev_appserver.pyを引数無しで実行したら無事にDBの保存先が判明。
エラーはこのファイルに対してアクセスできないから発生しているような気がする・・・。
そもそもWindows7ではユーザーアカウント制御があるからそれが原因っぽい。
なので対象となるフォルダを他のユーザでも操作できるように変更したら動いた!

以下は開発するときに利用するツールとか。

icon.jpg
GAE-PythonのSDKをインストールすると画面上に左の
ようなショートカットが表示される。




上記のショートカットをクリックすると新たに画面が立ち上がる。
sample_launch.jpg
この画面でたいていのことができる。
新しいアプリの作成、開発サーバでの実行、プログラムを編集、デプロイなど。
Eclipseよりは貧弱だけどコマンドプロンプトでちまちまやるよりは断然やり易い。
デフォルトのエディタだと非常にやり辛いのでとりあえずサクラエディタとかにしてみた。
そのうちmeadowかvimにすると思う。。
プログラムも今はwindows環境においてるけど、VMのCentOSにsambaでつないで
そっちにプログラムを置けばそっち側でgitでプログラムの管理できるのかも。
今のところLinux環境におく意味はそんなに無いか・・・。

開発環境でデータストアを利用して実験してるときに、今実際にデータストアに
どんなデータが入っているのかわからなくなる。
そんな時には開発サーバを起動させた状態で以下のURLにアクセス。

http://localhost:8080/_ah/admin/datastore

すると以下のような管理画面が表示される。
sample_admin.jpg

現在登録されているデータは「Entity Kind」を選択して「List Entities」をクリックすれば表示される。

sample_admin2.jpg

あと使うのはコマンドプロンプトで直接「dev_appserver.py」をたたく位かな?



Googleの用意してくれたスタートガイドは一通りやってみたので次は
サービス機能の利用に進む予定。

でもそもそもPythonがわからないからそれも同時並行で勉強しなきゃ。。
Googleのドキュメントも隅々まで読まないと全体像が把握できないんだろうな?。。



Google App Engine 実践リファレンス
清野 克行
技術評論社
売り上げランキング: 42428
おすすめ度の平均: 4.0
4 これから始めたい方に・・・JavaでもPythonでも

C言語:構造体

| コメント(1) | トラックバック(0)
続いては構造体について。

異なったデータ型をひとまとめにして扱いたい場合に構造体を利用する。

構造体の宣言は↓のような感じで行う。

struct 構造体タグ {
    メンバ;
    ・・・;
};

↑だけではまだ構造体のテンプレートを宣言しただけで使えない。
実際に使うには構造体タグをを使って構造体変数を宣言する必要がある。

struct data man;
     ↑   ↑
     タグ 変数

構造体テンプレートを宣言するのと同時に、構造体変数を宣言するやり方もある↓。
struct 構造体タグ{
    メンバ;
    ・・・;
} 構造体変数1, 構造体変数2, ・・・;

↑のように同時に宣言する場合は構造体タグを省略することもできる。

構造体のメンバへアクセスするには構造体メンバ演算子「.」を利用する。

構造体変数の配列を作ることも可能。

struct Man student;
と構造体変数を宣言していたところを
struct Man student[5];
とかすればいい。

student[2]のnameメンバにアクセスする場合は
student[2].name でOK。

構造体へのポインタ

構造体変数を

struct Man *student;

のように宣言すると、studentはManへの構造体へのポインタとなる。
このときメンバにアクセスするのは

(*student).name とする。

でも一般的にはアロー演算子を使って
student->name
とする。

新しい型を定義する

typedefキーワードを使えばデータ型の名前を自由に定義することができる。

typedef 既存の型名 新しい型名;

typedef int SEISU; こんな感じで。

上の例では何がうれしいのかあまりわからない・・・。

でも構造体にこのtypedefを適用するとあたかも新しい型ができたかのように見える。

たとえば

typedef struct _tagData {
    int age;
    double bl;
    double bw;
} MYDATA, *LPMYDATA;

とすると_tagData型の構造体変数を定義するとき、

MYDATA mydata;

と書ける。

構造体へのポインタを宣言する場合も
 
LPMYDATA lpmydata;

と宣言できる。



以上のことを踏まえてのサンプルプログラム↓。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct DATA {
        char name[32];
        char tel[32];
} MYDATA, *LPMYDATA;

int menu();
int meibo_input( LPMYDATA );
int meibo_output( LPMYDATA );

int main() {
        int selectno, loopend = 0;

        MYDATA mydata[10];
        LPMYDATA lpMydata = mydata;
        
        memset( mydata, '\0', sizeof(MYDATA[10]) );

        while(1) {
                selectno = menu();

                switch (selectno) {
                        case 1:
                                meibo_input( lpMydata );
                                break;
                        case 2:
                                meibo_output( lpMydata );
                                break;
                        default:
                                loopend = 1;
                                break;
                }
                if( loopend )
                        break;
        }
        return 0;
}

int menu() {
        char ret[8];
        
        printf( "****** MENU ******\n" );
        printf( "1:データ入力\n" );
        printf( "2:データ表示\n" );
        printf( "0:終了\n" );
        printf( "********************\n" );
        printf( "---->" );
        gets(ret);
        return atoi(ret);
}

int meibo_input( LPMYDATA lpData ) {
        char strno[8];
        int no;

        printf( "何番のデータを入力しますか(0-9)--" );
        gets(strno);
        if( strno[0] < '0' || strno[0] > '9' ) {
                printf( "入力が不正です\n" );
                return -1;
        }
        strno[1] = '\0';
        no = atoi(strno);

        printf("氏名--");
        gets( (lpData + no)->name );
        printf( "電話番号--" );
        gets( (lpData + no)->tel );

        return 0;
}

int meibo_output( LPMYDATA lpData ) {
        char strno[8];
        int no;
        
        printf( "何番のデータを読み出しますか(0-9)--" );
        gets( strno );
        
        if(strno[0] < '0' || strno[0] > '9') {
                printf( "入力が不正です\n" );
                return -1;
        }
        strno[1] = '\0';
        no = atoi( strno );

        if(!strcmp( (lpData + no )->name, "" )) {
                printf( "データがありません。\n" );
                return -1;
        }
        printf( "氏名-- %s\n", ( lpData + no )->name );
        printf( "電話番号-- %s\n", (lpData + no)->tel );

        return 0;
}



実行結果
$ ./type03.exe 
****** MENU ******
1:データ入力
2:データ表示
0:終了
********************
---->1
何番のデータを入力しますか(0-9)--3
氏名--test
電話番号--1234567
****** MENU ******
1:データ入力
2:データ表示
0:終了
********************
---->2
何番のデータを読み出しますか(0-9)--3
氏名-- test
電話番号-- 1234567
****** MENU ******
1:データ入力
2:データ表示
0:終了
********************
---->0


もうちょっと色々やってみないとしっくりこないな。


猫でもわかるC言語プログラミング 第2版 猫でもわかるプログラミングシリーズ (NEKO Series)
粂井 康孝
ソフトバンククリエイティブ
売り上げランキング: 30287
おすすめ度の平均: 3.0
3 後半の説明に難あり?
5 猫でもわかるC言語プログラミング
1 猫どころか人間でもわからない
5 実際にコードを書いて覚える本
1 ポインタや配列の説明が内容的に薄いかな。
ちょっと色々思うところがあり、GoogleのApp Engineをいまさらながら勉強してみようかと。。

ついでにPythonの勉強にもなればいいな。

というわけでGAE-Pythonの開発環境を設定してみる。

まずはPythonのインストールから。

Python公式サイトからGAE-Pythonでサポートされている2.5.xをダウンロード&インストールします。

Download  >  Releases

今回は参考本と同じようにpython-2.5.4.msiをダウンロードしてインストールします。

何も考えずに全部デフォルトでインストールしてみました。

次にGAEのPython SDKをダウンロードします。

GoogleAppEngine_1.3.3.msiをダウンロードしてインストール。

インストールしたフォルダの内容は以下のようになりました。

p_1.jpg
dev_appserver.py
これは開発用のWebサーバのようです。Googleのクラウド環境をエミュレートして動作確認ができるらしい。

appcfg.py
これでGAEのクラウド環境へのアップロード/デプロイを行うみたい。

ではデモ用のプログラムが用意されているようなので実行してみます。

コマンドプロンプトを起動してdev_appserver.pyのところまで移動します。
そして

dev_appserver.py demos\guestbook

と入力します。

p_2.jpg


画面のように最後にローカルのURLが表示されれば成功のようです。

実際にブラウザから以下にアクセスすると
http://localhost:8080/

p_3.jpg
こんな画面が表示されます。
適当に入力して「Sign Guestbook」をクリックすると、



An anonymous person wrote:

    テスト入力です。

とか表示されます。
これで一応動くところまでこぎつけました。

終了の方法は
「Ctrl+C」または「Ctrl+Break」みたいです。

今日はとりあえずここまで。





Google App Engine 実践リファレンス
清野 克行
技術評論社
売り上げランキング: 26659
おすすめ度の平均: 4.0
4 これから始めたい方に・・・JavaでもPythonでも

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        

ウェブページ

このアーカイブについて

このページには、過去に書かれたブログ記事のうちプログラムカテゴリに属しているものが含まれています。

前のカテゴリはブラウザです。

次のカテゴリは日記です。

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

Powered by Movable Type 5.01