PHP7でデザインパターン入門20/23 Flyweightパターン

Flyweight パターンとは

等価なインスタンスを別々の箇所で使用する際に、一つのインスタンスを再利用することによってプログラムを省リソース化することを目的とする。

Flyweight パターン - Wikipedia

flyweighという名がついているように、このデザインパターンは、オブジェクトを「軽く」するためのもの。 ここでいう重さとは、「メモリの使用量」のことを指す。たくさんメモリを使うオブジェクトを「重い」、省メモリのオブジェクトを「軽い」と表現する。

$obj = new Something();

Somethingクラスのインタスタンスを上記で作ることができる。このとき、そのインスタンスを保持するために、メモリが確保される。Somethingクラスのインスタンスがたくさん必要なときに、たくさんのインスタンスを生成してしまうと、メモリの使用量が大きくなる。

Flyweightパターンで使う技法は、一言で言えばインスタンスをできるだけ共有して、無駄に new しない」というもの。

example

以下Flyweghtパターンを使ったサンプルプログラム。 重いインスタンスを作るクラスとして「大きな文字」を表現するクラスを考える。

  • BigChar: 「大きな文字」を表すクラス
  • BigCharFactory: BigCharのインスタンスを共有しながら生成するクラス
  • BigString: BigCharを集めて作った「大きな文字列」を表すクラス
  • Main: テスト動作用

BigCharクラス

BigCharは、ファイルから大きな文字のテキストを読み込んでメモリ上に保持し、printメソッドでそれを表示する。

<?php

class BigChar
{
    const DATA_DIR = __DIR__ . "/Data/";
    private $charname;
    private $fontdata;

    public function __construct(string $charname)
    {
        $this->charname = $charname;
        try {
            $contents = file_get_contents(self::DATA_DIR . "big" . $charname . ".txt");
            if ($contents === false) {
                throw new Exception("the file big{$charname}.txt does not exist.");
            }
            $this->fontdata = $contents;
        } catch (Exception $e) {
            echo "error: " . $e->getMessage();
        }
    }

    public function print()
    {
        echo $this->fontdata;
    }
}

BigCharFactoryクラス

BigCharFactoryクラスは、BigCharクラスのインスタンスを作る。しかし、同じ文字に対応するBigCharクラスのインスタンスすでに作ってあった場合は、それを利用して、新しいインスタンスは作成しない。これまで作ったインスタンスはpoolプロパティに保持する。

<?php

class BigCharFactory
{
    private $pool = [];
    private static $singleton;

    private function __construct()
    {
    }

    public static function getInstance(): BigCharFactory
    {
        if (self::$singleton === null) {
            self::$singleton = new self;
        }
        return self::$singleton;
    }

    public function getBigChar(string $charname): BigChar
    {
        if (isset($this->pool[$charname]) === false) {
            $this->pool[$charname] = new BigChar($charname);
        }
        return $this->pool[$charname];
    }
}

BigStringクラス

BigStringはBigCharを集めて作った「大きな文字列」クラス。bigcharsプロパティはBigCharの配列であり、BigCharのインスタンスを保持する。

<?php

class BigString
{
    private $bigchars;

    public function __construct(string $string)
    {
        $this->bigchars = [];
        $factory = BigCharFactory::getInstance();
        for ($i = 0; $i < strlen($string); $i++) {
            $this->bigchars[$i] = $factory->getBigChar($string[$i]);
        }
    }

    public function print()
    {
        for ($i = 0; $i < count($this->bigchars); $i++) {
            $this->bigchars[$i]->print();
        }
    }
}

Main

<?php
$input = strval(trim(fgets(STDIN)));
if (strlen($input) === 0) {
    echo "[input error]. please input the example below." . PHP_EOL;
    echo "example: 1212123" . PHP_EOL;
    die();
}
$bs = new BigString($input);
$bs->print();

実行結果

$ php Main.php
input digits: 1234
......##........
..######........
......##........
......##........
......##........
......##........
..##########....
................
....######......
..##......##....
..........##....
......####......
....##..........
..##............
..##########....
................
....######......
..##......##....
..........##....
......####......
..........##....
..##......##....
....######......
................
........##......
......####......
....##..##......
..##....##......
..##########....
........##......
......######....
................

ソースコード

github.com

役割

Flyweight

普通に扱うとプログラムが重くなるので共有した方がよいものを表す役。 サンプルコードにおけるBigCharクラス。

FlyweightFactory役

Flyweight役を作る工場の役。この工場を使ってFlyweight役を作ると、インスタンスが共有される。 サンプルコードにおけるBigCharFactoryクラス。

Client役

FlyweightFactory役を使ってFlyweight役を作り出し、それを利用する役。 サンプルコードにおけるBigStringクラス。

まとめ

複数箇所に影響が及ぶ

Flyweightパターンではインスタンスを「共有」することがテーマ。インスタンスを共有するということは、共有しているものを変更すると、参照先全てに影響が及ぶということ。そのため、ほんとうに複数箇所に共有させるべき情報だけをFlyweight役に持たせるべき。

intrinsic と extrinsic を意識する

共有させる情報は、intrinsicな情報である。サンプルコードのBigCharフォントデータは、BigStringのどこに登場しても不変である。そのため、BigCharフォントデータはintrinsicな情報になる。 一方で、共有させない情報は、extrinsicな情報である。置く場所によって変化する情報は共有すべき情報ではない。Flyweightパターンを使う上では、これらを意識して区別する必要がある。

関連パターン

増補改訂版Java言語で学ぶデザインパターン入門

増補改訂版Java言語で学ぶデザインパターン入門

PHP7でデザインパターン入門19/23 Stateパターン

State パターンとは

振る舞いに関する(英語版) デザインパターンの一種である。このパターンはオブジェクトの状態(state)を表現するために用いられる。

State パターン - Wikipedia

Stateパターンでは、「状態」というものをクラスとして表現する。「状態」をクラスで表現していれば、クラスを切り替えることにより「状態の変化」を表すことができ、新しい状態を追加しなければならないときに何をプログラムすればよいかが明確になる。

example

時刻ごとに警備状態が変化する金庫警備システムを考える。 1時間ごとに擬似時刻を変化させ、その都度ランダムにアクションを実行させる。

仕様

  • 金庫が1つある
  • 金庫は警備センターと接続されている
  • 金庫には非常ベルト通常通話のようの電話が接続されている
  • 金庫には時計がついていて、現在時刻を監視している。
  • 昼間は9:00-16:59, 夜間は17:00-23:59および0:00-8:59の範囲
  • 金庫は昼間だけ利用可能
  • 昼間、金庫を利用すると、警備センターに使用記録が残る
  • 夜間、金庫を利用すると、警備センターに非常事態の通報が送信される
  • 非常ベルはいつでも使用可能
  • 非常ベルを使用すると、警備センターに非常事態の通報が送信される
  • 通常通話用の電話はいつでも使用可能。(が、夜間は録音のみ)
  • 昼間、電話を使用すると、警備センターが呼び出される
  • 夜間、電話を使用すると、警備センターの留守録が呼び出される

State インターフェース

金庫の状態を表す。以下のアクションに対応して呼び出されるインターフェースを定義している。

  • 時刻が設定されたとき
  • 金庫が使用されたとき
  • 非常ベルが押されたとき
  • 通常通話を行うとき
<?php
interface State
{
    public function doClock(Context $context, int $hour);
    public function doUse(Context $context);
    public function doAlarm(Context $context);
    public function doPhone(Context $context);
}

DayStateクラス

昼間の状態を表すクラス。状態を表すクラスは1つずつしかインスタンスを作らないことにする。 状態が変化するたびに新しいインスタンスを作るのはメモリと処理時間の無駄なのでSingletonパターンを使っている。

<?php

class DayState implements State
{
    private static $singleton;

    private function __construct()
    {
    }

    public static function getInstance(): State
    {
        if (self::$singleton === null) {
            self::$singleton = new self;
        }
        return self::$singleton;
    }

    public function doClock(Context $context, int $hour)
    {
        if ($hour < 9 || 17 <= $hour) {
            $context->changeState(NightState::getInstance());
        }
    }

    public function doUse(Context $context)
    {
        $context->recordLog("use the strongbox(daytime)");
    }

    public function doAlarm(Context $context)
    {
        $context->callSecurityCenter("security alert(daytime)");
    }

    public function doPhone(Context $context)
    {
        $context->callSecurityCenter("call security center(daytime)");
    }

    public function __toString(): string
    {
        return "[daytime]";
    }
}

NightStateクラス

夜間の状態を表すクラス。構成はDayStateクラスと同じ。

<?php

class NightState implements State
{
    private static $singleton;

    private function __construct()
    {
    }

    public static function getInstance(): State
    {
        if (self::$singleton === null) {
            self::$singleton = new self;
        }
        return self::$singleton;
    }

    public function doClock(Context $context, int $hour)
    {
        if (9 <= $hour && $hour < 17) {
            $context->changeState(DayState::getInstance());
        }
    }

    public function doUse(Context $context)
    {
        $context->callSecurityCenter("emergency:use the strongbox at nighttime");
    }

    public function doAlarm(Context $context)
    {
        $context->callSecurityCenter("security alert(nighttime)");
    }

    public function doPhone(Context $context)
    {
        $context->recordLog("record of the night call");
    }

    public function __toString(): string
    {
        return "[nighttime]";
    }
}

Contextクラス

状態の管理、警備センターへの呼び出しを行う。

<?php

class Context
{
    private $state;

    public function __construct(State $state = null)
    {
        if ($state instanceof State) {
            $this->state = $state;
        } else {
            $this->state = DayState::getInstance();
        }
    }

    public function setClock(int $hour)
    {
        $clockstring = "now: ";
        if ($hour < 10) {
            $clockstring .= "0" . $hour . ":00";
        } else {
            $clockstring .= $hour . ":00";
        }
        echo $clockstring . PHP_EOL;
        $this->state->doClock($this, $hour);
    }

    public function changeState(State $state)
    {
        echo "state is change from " . $this->state . " to " . $state . PHP_EOL;
        $this->state = $state;
    }

    public function callSecurityCenter(string $msg)
    {
        echo "call! " . $msg . PHP_EOL;
    }

    public function recordLog(string $msg)
    {
        echo "record ... " . $msg . PHP_EOL;
    }

    public function doRandomAction()
    {
        switch (rand(0, 2)) {
            case 0:
                $this->state->doUse($this);
                break;
            case 1:
                $this->state->doAlarm($this);
                break;
            case 2:
                $this->state->doPhone($this);
                break;
            default:
                break;
        }
    }
}

Main

テスト動作用

<?php

$context = new Context();
for ($hour = 0; $hour < 24; $hour++) {
    $context->setClock($hour);
    $context->doRandomAction();
    sleep(1);
}

実行結果

$ php Main.php
now: 00:00
state is change from [daytime] to [nighttime]
call! security alert(nighttime)
now: 01:00
call! emergency:use the strongbox at nighttime
now: 02:00
call! security alert(nighttime)
now: 03:00
call! security alert(nighttime)
now: 04:00
call! emergency:use the strongbox at nighttime
now: 05:00
call! emergency:use the strongbox at nighttime
now: 06:00
call! emergency:use the strongbox at nighttime
now: 07:00
call! emergency:use the strongbox at nighttime
now: 08:00
record ... record of the night call
now: 09:00
state is change from [nighttime] to [daytime]
call! call security center(daytime)
... 中略 ....
now: 18:00
call! emergency:use the strongbox at nighttime
now: 19:00
call! security alert(nighttime)
now: 20:00
record ... record of the night call
now: 21:00
call! security alert(nighttime)
now: 22:00
call! emergency:use the strongbox at nighttime
now: 23:00
record ... record of the night call

ソースコード

github.com

役割

State役

状態を表す。状態毎に異なる振る舞いをするインターフェースを定義する。サンプルコードにおけるStateインターフェース。

ConcreteState役

具体的な個々の状態を表現する。State役で定められたインターフェースを具体的に実装する。 サンプルコードにおけるDayState, NightStateクラス。

Context役

現在の状態を表すConcreteState役を持つ。 サンプルコードにおけるContextクラス。

まとめ

分割統治

Stateパターンは、システムの状態をクラスとして表現することで、複雑なプログラムを分割する

状態に依存した処理

Stateインターフェースで宣言されているメソッドはすべて「状態に依存した処理」である。「状態に依存した処理」は、プログラム上で以下のように表現される。

  • 抽象メソッドとして宣言し、インターフェースとする。
  • 具象メソッドとして実装し、個々のクラスとする。

自己矛盾が起こらない

Stateパターンでは状態をクラスで表現する。現在の状態を表す変数はたった1つである。サンプルコードではstateプロパティがシステムの状態を決定している。

新しい状態の追加は容易

サンプルコードで言えば、Stateインターフェースを実装したXXXStateクラスの追加は容易。逆にStateインターフェースに新しいメソッドを追加することは、全てのXXXStateクラスへの変更が必要となり、困難となる。

関連パターン

増補改訂版Java言語で学ぶデザインパターン入門

増補改訂版Java言語で学ぶデザインパターン入門

PHP7でデザインパターン入門18/23 Mementoパターン

Memento パターンとは

オブジェクトを以前の状態に(ロールバックにより)戻す能力を提供する。

Memento パターン - Wikipedia

オブジェクト指向のプログラムでundo(やり直し)を実現するには、インスタンスの持っている情報を保存しておく必要がある。 ただし、保存しておくだけでは駄目で、保存しておいた情報からインスタンスを元の状態に戻せなければならない。

インスタンスを復元するためには、インスタンス内部の情報にアクセスできる必要がある。しかし、不用意にアクセスを許可してしまうと、そのクラスの内部構造に依存したコードがプログラムのあちこちに散らばり、クラスの修正がしにくくなってしまう。これをカプセル化の破壊と言う。

インスタンスの状態を表す役割を導入し、カプセル化の破壊に陥ることなく保存と復元を行うのが、Mementoパターンである。

Mementoパターンを用いると、プログラムに対して

  • undo(やり直し)
  • redo(再実行)
  • history(作業履歴の作成)
  • snapshot(現在の状態の保存)

example

Mementoパターンを使ったサンプルプログラム「フルーツを集めていくサイコロゲーム」を作成する。

このゲームのルールは以下の通り

  • このゲームは自動進行する
  • 主人公はサイコロを振り、その目が次の状態を決定する
  • サイコロの目によって、
    • お金が増える
    • お金が減る
    • フルーツを貰う
  • お金がなくなったら終了する

サンプルコード中では、お金が貯まったところで、将来のためにMementoクラスのインスタンスを作成し、「現在の状態」を保存する。 保存するのは現在持っているお金とフルーツ。もし負け続けてお金が減ってきたら、お金がなくなって終了しないように、以前保存しておいたMementoのインスタンスを使って、以前の状態を復元する。

Memento クラス

主人公(Gamer)の状態を表現するクラス。

<?php
class Memento
{
    public $money;
    public $fruits = [];

    public function __construct(int $money)
    {
        $this->money = $money;
    }

    public function getMoney(): int
    {
        return $this->money;
    }

    public function addFruit(string $fruit)
    {
        $this->fruits[] = $fruit;
    }

    public function getFruits(): array
    {
        return $this->fruits;
    }
}

Gamer クラス

ゲームを行う主人公を表現しているクラス。

  • createMementoメソッドは、現在の状態を保存する(snapshot)。このメソッドはMementoを作成する。フルーツについては、「おいしい」ものだけを保存している。
  • restoreMementoメソッドは、createMementoメソッドの逆で、undoを行う。与えられたMementoのインスタンスを元に、自身の状態を復元する。
<?php
class Gamer
{
    private $money;
    private $fruits = [];
    const FRUITS_NAME = ["リンゴ", "ぶどう", "バナナ", "みかん"];

    public function __construct(int $money)
    {
        $this->money = $money;
    }

    public function getMoney(): int
    {
        return $this->money;
    }

    public function bet()
    {
        switch (rand(1, 6)) {
            case 1:
                $this->money += 100;
                echo "所持金が増えました。" . PHP_EOL;
                break;
            case 2:
                $this->money /= 2;
                echo "所持金が半分になりました。" . PHP_EOL;
                break;
            case 6:
                $f = $this->getFruit();
                echo "フルーツ(" . $f . ")をもらいました。" . PHP_EOL;
                $this->fruits[] = $f;
                break;
            default:
                echo "何も起こりませんでした。" . PHP_EOL;
        }
    }

    public function createMemento(): Memento
    {
        $m = new Memento($this->money);
        foreach ($this->fruits as $v) {
            if (substr($v, 0, strlen("おいしい")) === "おいしい") {
                $m->addFruit($v);
            }
        }
        return $m;
    }

    public function restoreMemento(Memento $memento)
    {
        $this->money = $memento->money;
        $this->fruits = $memento->getFruits();
    }

    public function __toString(): string
    {
        return "[money = " . $this->money . ", fruits = " . join(', ', $this->fruits) . "]";
    }

    private function getFruit(): string
    {
        $suffix = boolval(rand(0, 1)) ? "おいしい" : "";
        return $suffix . self::FRUITS_NAME[rand(0, count(self::FRUITS_NAME)-1)];
    }
}

Main

Gamerのインスタンスを作成し、それを使ってゲームを行う。Gamerのbetメソッドを繰り返し呼び出し、その度に現在の状態を表示する。

Mementoパターンを導入し

  • 変数mementoに「ある時点のGamerの状態」を保存する。
  • お金が増えてきたら、createMementoを使って現在の状態を保存する。
  • お金が足りなくなってきたら、restoreMementoメソッドにこのmementoを与えて所持金を元に戻す。
<?php
$gamer = new Gamer(100);
$memento = $gamer->createMemento();
for ($i = 0; $i < 50; $i++) {
    echo "==== " . $i . PHP_EOL;
    echo "現状:" . $gamer . PHP_EOL;
    $gamer->bet();
    echo "所持金は" . $gamer->getMoney() . "円になりました。" . PHP_EOL;
    if ($gamer->getMoney() > $memento->getMoney()) {
        echo "    (だいぶ増えたので、現在の状態を保存しておこう)" . PHP_EOL;
        $memento = $gamer->createMemento();
    } elseif ($gamer->getMoney() < $memento->getMoney() / 2) {
        echo "    (だいぶ減ったので、以前の状態に復帰しよう)" . PHP_EOL;
        $gamer->restoreMemento($memento);
    }
    sleep(1);
    echo PHP_EOL;
}

実行結果

$ php main.php
==== 0
現状:[money = 100, fruits = ]
所持金が半分になりました。
所持金は50円になりました。

==== 1
現状:[money = 50, fruits = ]
所持金が半分になりました。
所持金は25円になりました。
    (だいぶ減ったので、以前の状態に復帰しよう)

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

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

==== 4
現状:[money = 300, fruits = ]
所持金が半分になりました。
所持金は150円になりました。

==== 5
現状:[money = 150, fruits = ]
フルーツ(リンゴ)をもらいました。
所持金は150円になりました。

==== 6
現状:[money = 150, fruits = リンゴ]
何も起こりませんでした。
所持金は150円になりました。

==== 7
現状:[money = 150, fruits = リンゴ]
何も起こりませんでした。
所持金は150円になりました。

==== 8
現状:[money = 150, fruits = リンゴ]
何も起こりませんでした。
所持金は150円になりました。

==== 9
現状:[money = 150, fruits = リンゴ]
何も起こりませんでした。
所持金は150円になりました。

==== 10
現状:[money = 150, fruits = リンゴ]
所持金が増えました。
所持金は250円になりました。

... 中略 ...

==== 40
現状:[money = 275, fruits = おいしいみかん, ぶどう, バナナ]
所持金が増えました。
所持金は375円になりました。

==== 41
現状:[money = 375, fruits = おいしいみかん, ぶどう, バナナ]
何も起こりませんでした。
所持金は375円になりました。

==== 42
現状:[money = 375, fruits = おいしいみかん, ぶどう, バナナ]
何も起こりませんでした。
所持金は375円になりました。

==== 43
現状:[money = 375, fruits = おいしいみかん, ぶどう, バナナ]
フルーツ(みかん)をもらいました。
所持金は375円になりました。

==== 44
現状:[money = 375, fruits = おいしいみかん, ぶどう, バナナ, みかん]
所持金が半分になりました。
所持金は187円になりました。
    (だいぶ減ったので、以前の状態に復帰しよう)

==== 45
現状:[money = 550, fruits = おいしいみかん]
何も起こりませんでした。
所持金は550円になりました。

==== 46
現状:[money = 550, fruits = おいしいみかん]
所持金が半分になりました。
所持金は275円になりました。

==== 47
現状:[money = 275, fruits = おいしいみかん]
所持金が半分になりました。
所持金は137円になりました。
    (だいぶ減ったので、以前の状態に復帰しよう)

==== 48
現状:[money = 550, fruits = おいしいみかん]
所持金が半分になりました。
所持金は275円になりました。

==== 49
現状:[money = 275, fruits = おいしいみかん]
所持金が半分になりました。
所持金は137円になりました。
    (だいぶ減ったので、以前の状態に復帰しよう)

ソースコード

github.com

役割

Originator役

この役は、自分の現在の状態を保存したいときにMemento役を作る。 Originator役はまた、以前のMemento役を渡されると、そのMemento役を作った時点の状態に戻る処理を行う。 サンプルコードにおけるGamerクラス。

Memento役

この役は、Originator役の内部情報をまとめる。Memento役はOriginator役の内部情報を持っているが、その情報を誰にでも公開するわけではない。Memento役は次の2種類のインターフェースを持っている。

  • wide interface オブジェクトの状態を元に戻すために必要な情報がすべて得られるメソッドの集合。これを使えるのはOriginator役のみ。

  • narrow interface 外部のCaretaker役に見せるもの。内部状態が外部に公開されるのを防ぐ。

この2種類のインターフェースを使い分けることでオブジェクトのカプセル化の破壊を防ぐことができる。 この役はサンプルコードにおけるMementoクラス。

Caretaker役

現在のOriginator役の状態を保存したいときに、そのことをOriginator役に伝える。Originator役はそれを受けてMementoを作り、Caretaker役に渡す。Caretaker役は将来の必要に備えて、そのMemento役を保存しておく。 サンプルコードにおけるMain.php

まとめ

  • サンプルコードでは1つのMementoしか保存しなかった(上書きで最新保存)が、Mementoのインスタンスを複数個持つようにすれば、インスタンスの様々な時点での状態を保存できる。
  • Memento役をCaretaker役が保存しておき、必要なときにはMemento役を取り出して、Originator役に渡すことで復元ができる。これがMementoパターン。

関連パターン

増補改訂版Java言語で学ぶデザインパターン入門

増補改訂版Java言語で学ぶデザインパターン入門

PHP7でデザインパターン入門17/23 Observerパターン

Observer パターンとは

プログラム内のオブジェクトの状態を観察(英: observe)するようなプログラムで使われるデザインパターンの一種。出版-購読型モデルとも呼ばれる。

Observer パターン - Wikipedia

観察対象の状態をが変化すると、観察者に対して通知される。Observerパターンは状態変化に応じた処理を記述するときに有効である。

example

数を生成するオブジェクトを観察者が観察して、その値を表示するというプログラムを作成する。 ただし、表示方法は観察者によって異なる。DigitObserverは値で数字を表示し、GraphObserverは値を簡易グラフで表示する。

Observer インターフェース

観察者を表現するインタフェース。

<?php
interface Observer
{
    public function update(NumberGenerator $generator);
}

NumberGenerator クラス

数を生成する抽象クラス。

<?php
abstract class NumberGenerator
{
    private $observers = [];

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

    public function displayObservers()
    {
        echo "Observers:" . PHP_EOL;
        foreach($this->observers as $k => $v) {
            echo $k+1 . ":" . get_class($v) . PHP_EOL;
        }
        echo PHP_EOL;
    }

    public function deleteObserver(Observer $observer)
    {
        foreach ($this->observers as $k => $v) {
            if ($v === $observer) {
                unset($this->observers[$k]);
                return;
            }
        }
    }
    public function notifyObservers()
    {
        foreach ($this->observers as $observer) {
            $observer->update($this);
        }
    }

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

RandomNumberGenerator クラス

NumberGeneratorのサブクラスで、乱数を生成する。

<?php
class RandomNumberGenerator extends NumberGenerator
{
    private $number;

    public function getNumber(): int
    {
        return $this->number;
    }

    public function execute()
    {
        $this->displayObservers();
        for ($i = 0; $i < 10; $i++) {
            $this->number = rand(0, 30);
            $this->notifyObservers();
        }
    }
}

DigitObserver クラス

Observerインターフェースを実装しているクラスで、観察した数を「数字」で表示する。sleep(1) は表示の様子をわかりやすくするため。

<?php
class DigitObserver implements Observer
{
    public function update(NumberGenerator $generator)
    {
        echo "DigitObserver:" . $generator->getNumber() . PHP_EOL;
        sleep(1);
    }
}

GraphObserver クラス

Observerインターフェースを実装するクラス。

<?php
class GraphObserver implements Observer
{
    public function update(NumberGenerator $generator)
    {
        echo "GraphObserver:";
        for ($i = 0; $i < $generator->getNumber(); $i++) {
            echo "*";
        }
        echo PHP_EOL;
        sleep(1);
    }
}

Main

テスト動作用

<?php
$generator = new RandomNumberGenerator();
$observer1 = new DigitObserver();
$observer2 = new GraphObserver();
$generator->addObserver($observer1);
$generator->addObserver($observer2);
$generator->execute();
$generator->deleteObserver($observer2);
$generator->execute();

実行結果

$ php Main.php
Observers:
1:DigitObserver
2:GraphObserver

DigitObserver:5
GraphObserver:*****
DigitObserver:23
GraphObserver:***********************
DigitObserver:7
GraphObserver:*******
DigitObserver:19
GraphObserver:*******************
DigitObserver:14
GraphObserver:**************
DigitObserver:19
GraphObserver:*******************
DigitObserver:22
GraphObserver:**********************
DigitObserver:18
GraphObserver:******************
DigitObserver:9
GraphObserver:*********
DigitObserver:3
GraphObserver:***
Observers:
1:DigitObserver

DigitObserver:20
DigitObserver:12
DigitObserver:3
DigitObserver:20
DigitObserver:9
DigitObserver:8
DigitObserver:12
DigitObserver:17
DigitObserver:19
DigitObserver:13

ソースコード

github.com

役割

Subject役

Subject役は観察される側を表す。観察者であるObserver役を登録するメソッドと、削除するメソッドを持っている。 サンプルコードにおけるNumberGeneratorクラス。

ConcreteSubject役

具体的な観察される側を表す。状態が変化したら、そのことを登録されているObserver役に伝える。 サンプルコードにおけるRandomNumberGeneratorクラス。

Observer役

Subject役から、「状態が変化した」と通知してもらう役。そのためのメソッドがupdateメソッド。 サンプルコードにおけるObserverインターフェース。

ConcreteObserver役

具体的なObserver。updateメソッドが呼び出されると、そのメソッドの中でSubject役の現在の状態を取得する。 サンプルコードにおけるDigitObserverクラス、GraphObserverクラス。

まとめ

「観察」というより「通知」。Observer役はSubject役から「通知」されるのを受動的に待っていることになる。そのためObserverパターンはPublish-Subscribeパターンと呼ばれることもある。

関連パターン

増補改訂版Java言語で学ぶデザインパターン入門

増補改訂版Java言語で学ぶデザインパターン入門

PHP7でデザインパターン入門16/23 Mediatorパターン

Mediator パターンとは

Mediator パターンを用いると、オブジェクト間の通信は mediator によってカプセル化され、オブジェクト同士で直接通信せず、mediator を介して行うようになる。

Mediator パターン - Wikipedia

mediatorとは、「調停者」「仲介者」という意味。グループにおける「相談役」のようなイメージ。メンバーは出来事を相談役へ報告し、相談役はその報告をもとに判断を下し、各メンバーに指示を出す。mediatorパターンとは以上のようなデザインパターンである。

example

相談者が相談を引き受け、それを元に判断しColleagueへ指示を出すプログラムを考える。以下サンプルコード。

github.com

実行結果

$ php Main.php
consultation form John
ConcreteColleagueTypeB: consulation from John
consultation form Sarah
ConcreteColleagueTypeA: consulation from Sarah

役割

Mediator役

Colleague役とやり取りを行う、調整を行うためのインターフェース。 サンプルコードにおけるMediatorインターフェース。

ConcreteMediator役

Mediator役のインターフェースを実装し、実際の調整を行う。 サンプルコードにおけるConcreteMediatorクラス。

Colleague役

Mediator役とやり取りを行うインターフェースを定める。 サンプルコードにおけるColleagueインターフェース。

ConcreteColleague役

Colleague役のインターフェースを実装する。 サンプルコードにおけるConcreteColleagueTypeA, ConcreteColleagueTypeBクラス。

まとめ

Mediatorパターンは、複雑に絡みあうオブジェクト相互の通信をやめ、Mediator役に情報を集中させることによって処理を整理する。

関連パターン

増補改訂版Java言語で学ぶデザインパターン入門

増補改訂版Java言語で学ぶデザインパターン入門

PHP7でデザインパターン入門15/23 Facadeパターン

Facade パターンとは

Facade(ファサード)とは「建物の正面」を意味する。異なるサブシステムを単純な操作だけを持ったFacadeクラスで結び、サブシステム間の独立性を高める事を目的とする。

Facade パターン - Wikipedia

プログラムというのは段々大きくなる傾向がある。たくさんのクラスが作られ、相互に関係し、複雑さを増していく。 大きなプログラムを使って処理を行うためには、そのたくさんのクラスを適切に制御しなければならない。 その処理を行うための「窓口」を用意しておく。そうすればたくさんのクラスを個別に制御しなくても、その窓口に対して要求を出すだけで仕事が済む。

example

ユーザのWebページを作成するサンプルコードを作る。 Facadeパターンのサンプルを作るためには、本来「複雑に入り組んだたくさんのクラス」が必要にある。しかし、ここでは簡単のため3つのクラスだけからなるシステムを考える。

Databaseクラス

メールアドレスから名前を得る。

HtmlWriter

HTMLファイルを作成する。

PageMaker

Facade役として高レベルのインターフェースを提供する。

以下サンプルコード。

github.com

実行結果

$ php Main.php
Welcome.html is created for example01@example.com (eample01)
$ cat Welcome.html
<html>
<head>
<title>Welcome to the eample01's page!</title>
</head>
<body>
<h1>Welcome to the eample01's page!</h1>
<p>Welcome to the eample01's page!</p>
<p>I'm looking forward to hearing from you.</p>
<p><a href="mailto:example01@example.com">eample01</a></p>
</body>
</html>

役割

Facade役

システムを構成しているその他大勢の役の「シンプルな窓口」となる。高レベルでシンプルなインターフェースをシステム外部に提供する。 サンプルコードにおけるPageMakerクラス。

システムを構成してるその他大勢の役

その他大勢の役は、それぞれの仕事を行うが、Facade役のことは意識しない。Facade役から呼び出されるが、この役からFacade役を呼び出すことはない。サンプルコードにおけるDatabase, HtmlWriterクラス。

Client役

Facadeを利用する役。 サンプルコードにおけるMain。

まとめ

再帰的なFacadeパターン

Facade役を持ったクラスの集合が複数あるとする。そのとき、それらの集合をさらにまとめたFacade役を作ることもできる。

Facade役のやっていること

Facade役は、インターフェースを単純にすることで複雑なものを単純に見せている。そうすることで複雑なシステムに対するシンプルな窓口となる。

関連パターン

増補改訂版Java言語で学ぶデザインパターン入門

増補改訂版Java言語で学ぶデザインパターン入門

第220回TOEIC結果

結果

score: 770

  • listening: 405 (+5)
  • reading: 365 (+25)

f:id:tic40:20170612202711p:plain

スコア推移

伸びてるのか最近はよくわからなくなってきたので、2015年からのテスト結果をスプレッドシートでグラフ化してみた。亀のようなスピードで伸びていることがわかった。

f:id:tic40:20170612211610p:plain

感想

part7長文問題の解き方を若干変更して挑んだ。

今までは、問題をざっと見る->本文章を全部読む->問題を解く

だったのを、

最初の問を読んで、その答えを文章から探す->次の問を読んで文章を続きから読む

というやり方に変えた。

誤差の範囲かもしれないがreadingだけ見れば365は過去最高点だった。

次回受験

2017/6/21 第221回。点数があまりにも上がらなくて、今TOEICモチベーションが最低