Do You PHP はてな このページをアンテナに追加 RSSフィード Twitter

2015-02-24

[][]runkit with PHP5.6.x

すでにpecl.php.netではメンテされていないrunkitですが、GitHubに移行したハズが現状ではmasterブランチでさえPHP5.6.xでbuildできません。これはissueにも上がっています。

で、これに対するpullリクエストもすでに上がっているんですが、本体へのマージのところで止まってしまっているようです。。。

なので、PHP5.6.xでrunkitを使いたい方は、pullリクエストに上げた方のブランチをcloneして使いましょう

なお、buildするにはgcc-4.6以降が必要なようで、CentOS6.xだと別途インストールする必要があります。今回はCentOS6.4にredhat developer toolsetをインストールしました。手順としては以下のような感じ。

$ cat /etc/issue
$ CentOS release 6.4 (Final)
$ Kernel \r on an \m
$
$ wget http://people.centos.org/tru/devtools-1.1/devtools-1.1.repo 
$ sudo yum --enablerepo=testing-1.1-devtools-6 install devtoolset-1.1-gcc devtoolset-1.1-gcc-c++
$
$ git clone -b fix_php56 https://github.com/davidsteinsland/runkit.git
$ cd runkit/
$ phpize
$ CC=/opt/centos/devtoolset-1.1/root/usr/bin/gcc CPP=/opt/centos/devtoolset-1.1/root/usr/bin/cpp CXX=/opt/centos/devtoolset-1.1/root/usr/bin/c++ ./configure
$ make
$ sudo make install
$ 
$ sudo echo 'extension=runkit.so' >> /path/to/php.ini
$
$ php -v
PHP 5.6.6 (cli) (built: Feb 19 2015 18:40:44)
Copyright (c) 1997-2015 The PHP Group
Zend Engine v2.6.0, Copyright (c) 1998-2015 Zend Technologies
    with Zend OPcache v7.0.4-dev, Copyright (c) 1999-2015, by Zend Technologies
    with Xdebug v2.2.7, Copyright (c) 2002-2015, by Derick Rethans
$
$ php -m | grep runkit
runkit
$ 

これで無事PHP5.6でのテストができます:-)

2015-02-20

[]Cache-Controlヘッダがno-cacheな画像ファイルにFirefoxで直接アクセスした場合に2回リクエストを送信させない

何だか訳の分からないニッチ案件ですが。。。

Cache-Controlヘッダがno-cacheな画像ファイルにFirefoxで直接アクセスした場合、LiveHTTPHeadersで確認すると2回リクエストが送信されてるんですよね。。。1回目は通常のリクエスト、2回目はFirefoxのタブに縮小版を表示させるための画像を取得するため(?)のリクエストっぽいんですが、2回リクエストが送信されているのでApacheアクセスログにも当然2行残ります。

ちなみに、Chromeだと1回しかリクエストが送信されません。

で、こういう状況でFirefoxに2回リクエストさせない、かつ、毎回HTTP 200を返すようにするにはどうしたらいいか、ちょっと試してみました。

環境

httpd.confの初期設定

こんな感じに画像ファイルのみ"Cache-Control: no-cache"が返るように設定。

<FilesMatch \.(?i:gif|jpe?g|png)$>
    Header set Cache-Control no-cache
</FilesMatch>

とりあえず調査してみる

Cache-Controlの設定を変更してみたところ、

  • Cache-Controlヘッダをno-cacheにするとブラウザ側でキャッシュするが、タブに縮小表示する際に画像を再利用してくれればいいんだけど、"no-cache"の仕様上画像の再検証行う必要があるため、結果的に2回リクエストしてしまう
    • access_logにはHTTP 200、HTTP 304の2つのログが出力される
  • 一方、Cache-Controlヘッダをno-storeにすると、ホントにブラウザ側で画像ファイルをキャッシュしない、つまり、タブに縮小表示する画像がないので、再度リクエストして画像を取得する必要がある。これも結果的に2回リクエストを行う
    • access_logにはHTTP 200なログが2回出力される

な感じだと判明。

となると、

ブラウザにはキャッシュさせつつ、1回目のリクエスト時に画像ファイルが更新されたように見せかける

ってのが良いんじゃないかと。そうすれば、タブ表示の際は再検証不要の画像がキャッシュに残っているので余計な事はしなくなるハズ。。。

早速、試してみた

まず、ブラウザに画像をキャッシュさせるために

Header set Cache-Control no-cache

コメントアウト

続いて、「画像ファイルが更新されたように見せかける」部分は、ETagレスポンスヘッダを送信しないようにすれば良いんじゃないかと思い、以下を追加。

FileEtag None

ブラウザキャッシュをクリアしてアクセスしたところ、Firefoxからのリクエストが1回に!うまくキャッシュを使ってくれている模様。ただし、ステータスコード

  • 1回目:HTTP 200
  • 2回目以降:HTTP 304

に。。。惜しい!

思いつく他の方法としては、

  • Last-Modifiedレスポンスヘッダ
  • If-Modified-Sinceリクエストヘッダ

の組み合わせかと。

前者の場合はリクエスト毎に未来の日時を送信する。。。うーん、面倒そう(^^;

なので、If-Modified-Sinceリクエストヘッダを無理矢理無視することに。

RequestHeader unset If-Modified-Since

ブラウザキャッシュをクリアしてアクセスしたところ、Firefoxからのリクエストが1回、かつ、再読み込みしてもステータスコードHTTP 200になりました。

いや、ちょっと待て

そもそも、If-Modified-Sinceリクエストヘッダを過去日時の固定値にしたらいいんじゃねーの?

RequestHeader set If-Modified-Since "Mon, 1 Jan 2001 00:00:00 GMT"

再度キャッシュをクリアしてアクセスしてみたところ、先ほどと同様、リクエスト1回、かつ、再読み込みしてもステータスコードHTTP 200になりました。

最終的なhttpd.confの設定

Cache-Controlレスポンスヘッダではなく、If-Modified-Sinceリクエストヘッダを書き換えるようになりました。

<FilesMatch \.(?i:gif|jpe?g|png)$>
    RequestHeader set If-Modified-Since "Mon, 1 Jan 2001 00:00:00 GMT"
</FilesMatch>

ところで

Firefoxの2回リクエストするのって、何なんだろか。仕様なのかねぇ。。。:-(

2015-02-16

[]USB3.0な外付けSSD Transcend TS512GESD400K を買った

最近開発で使っているノートPCを新調したんですが、

  • 開発用VMwareイメージが思ったよりディスク容量を喰っている
  • イメージファイルがぶっ飛ぶと、致命傷ではないけど環境再構築が面倒なのでバックアップも取っておきたい
  • USB3.0ポート余ってるし、外付けSSDとかに移動すりゃいいんじゃね?そうすりゃ、イメージのバックアップも取れる

ということで、USB3.0外付けSSDを買うことに。


ところで。

開発者が10人いれば10通りの開発環境がある。。。そこまでは多分ないでしょうけど、皆さんどうやってます?

良くある(?)LAMPとかLAPPなら、VMwareVirtualBox+Vagrantなんかの仮想マシン1台で足りるかもしれません。私自身もVMware+LAPPだったんですが、うちの会社で提供しているサービスでPostgreSQL以外にHadoopクラスタ(CDH:Cloudera's Distribution Including Apache Hadoop)を使うようになったんで、やはり開発用のマイCDHが欲しいなぁ、と。そうなると、QuickStart VM使うのが手っ取り早いんですが、イメージサイズがでかいしバックアップも面倒だし。。。かといって、母艦にイメージをコピーするとあっという間にディスク使い切りそうだし。。。



閑話休題

Amazonのレビューでいろいろ眺めて、評価とコストパフォーマンスが良さそうなTranscendSSDに決定。容量は512GB。

レビュー(っぽいもの)

今日届いたので、早速試してみました。パッケージはこんな感じ。

f:id:shimooka:20150216130245j:image

中央が本体。ちょっと白っぽく写ってますが、本体は濃いグレーです。付属していたのはUSBケーブルとポーチ。あとは説明書などです。

f:id:shimooka:20150216130827j:image

本体の大きさは、ほぼ名刺サイズ。これなら持ち運びも問題なし!

f:id:shimooka:20150216130857j:image

ケーブルは結構太め。

f:id:shimooka:20150216130919j:image

USB3.0接続時は青いランプが点灯します。USB2.0の場合は緑色。

f:id:shimooka:20150216131049j:image

ポーチですが、本体の頭がちょっと出てしまうぐらい小さめです。

f:id:shimooka:20150216144837j:image

パフォーマンス

CrystalDiskMark 3.0.3(64ビット版)での結果です。まっさらの状態でのテストではないんですが。。。

f:id:shimooka:20150216153019p:image

-----------------------------------------------------------------------
CrystalDiskMark 3.0.3 x64 (C) 2007-2013 hiyohiyo
                           Crystal Dew World : http://crystalmark.info/
-----------------------------------------------------------------------
* MB/s = 1,000,000 byte/s [SATA/300 = 300,000,000 byte/s]

           Sequential Read :   296.208 MB/s
          Sequential Write :   278.248 MB/s
         Random Read 512KB :   224.062 MB/s
        Random Write 512KB :   172.154 MB/s
    Random Read 4KB (QD=1) :    20.163 MB/s [  4922.7 IOPS]
   Random Write 4KB (QD=1) :    34.889 MB/s [  8517.7 IOPS]
   Random Read 4KB (QD=32) :    21.332 MB/s [  5208.1 IOPS]
  Random Write 4KB (QD=32) :    45.540 MB/s [ 11118.1 IOPS]

  Test : 1000 MB [D: 25.0% (119.0/476.8 GB)] (x5)
  Date : 2015/02/16 14:31:20
    OS : Windows 7 Professional SP1 [6.1 Build 7601] (x64)

まあ、SATAに繋いだSSDに比べるとアレですが、充分じゃないでしょうかね。

まとめ

早速、VMwareイメージを移動して外付けSSDから起動してみました。開発用サーバVMなのでI/Oガリガリとかないのもありますが、全然ストレスなく使えています。母艦は消耗品、データが大事と思っているので、最終的にはVMwareイメージ以外にもいろいろとバックアップしておこうかと。

欠点といえばケーブルの太さなんですが、まあ、これは転送スピードを考えるとしょうがないと割り切ってます。

にしても、SSDも安くなりましたねぇ。。。

2014-12-19

[][]PHPによるデザインパターン入門 - Visitor〜要素と要素に対する操作を分離する

このエントリは、Do You PHP?(www.doyouphp.jp)で公開していたコンテンツを移行/加筆/修正したものです。公開の経緯はこちらをどうぞ。目次はこちらです。サンプルコードを手直ししたものgithubに上げてありますのでそちらもどうぞ。

GoF本における分類

 振る舞い+オブジェクト

はじめに

 ここではVisitorパターンについて説明します。

 「visitor」とは「visit」する人、つまり「訪問者」という意味ですね。Visitorパターンは、訪問者に相当するクラスが登場します。

 では、この訪問者は誰を訪問し、何をするのでしょうか?

 それをこれから見ていきましょう。

たとえば

 ファイルシステムディレクトリツリーのようなあるデータ構造の各要素に対して、何らかの処理をおこなう場合を考えてみましょう。この場合、要素はディレクトリ(フォルダ)やファイルになります。

 これら要素に対する処理として、ファイルサイズの合計を取得するとします。この場合、情報のカプセル化やコードを書く効率を考えると、自身のサイズを返すAPIを用意し、このAPI再帰的に呼び出すことで比較的容易に実装できそうです。つまり、処理コードをそれぞれの要素の中に書いてしまう、ということです。

 しかし、新しい処理を追加する必要に迫られた場合はどうでしょうか?

 そうですね。それぞれの要素の中に処理コードを書いていますので、新しい処理のコードを追記する必要があります。要素の数が多い場合や追加するコードが複雑な場合は非常にコストのかかる作業になってしまいます。

 これは、データ構造とそれに対する処理が同居してしまっているため、どちらかを修正した場合に他方にも影響が出てしまっている状態だと言えます。

 ここで、データ構造と処理を分けておけば、新しい操作を追加する場合もデータ構造を変更する必要がなくなることが期待できます。また、分けることでそれぞれのコードに集中することができ、コードもシンプルになりそうです。

 これから詳しく見ていくVisitorパターンは、データ構造から分離された「操作」がデータ構造を渡り歩き、順に処理をおこなっていくという一風変わった動作をします。

 そして、この「操作」を表すオブジェクトが「訪問者」(visitor)として振る舞っているように見えることから、visitorという名が付いています。

Visitorパターンとは?

 Visitorパターンはオブジェクトの振る舞いに注目したパターンで、データ構造上の要素とそれに対する操作を分離することを目的としています。

 GoF本では、Visitorパターンの目的は次のように定義されています。

あるオブジェクト構造上の要素で実行されるオペレーションを表現する。Visitor パターンにより、オペレーションを加えるオブジェクトのクラスに変更を加えずに、新しいオペレーションを定義することができるようになる。

 Visitorパターンは、データ構造を表すクラス階層操作を表すクラス階層から構成されます。

 データ構造のクラス階層には当然、データ構造を表すための関連やメソッドが用意されますが、訪問者を受け入れるためのメソッドが別に用意されます。一方の訪問者である操作のクラス階層には、データ構造上の各要素に対する処理が実装されます。そして、操作クラスがデータ構造クラスに受け入れられたとき、データ構造の要素は「訪問者」である操作クラスに具体的な操作を依頼します。こうすることで、それぞれの要素を処理可能になります。

 では、「データ構造を訪問者が渡り歩く」ためのコードはどこに書けば良いでしょうか?一概には言えませんが、次の3つのうちいずれかに実装することになります。それぞれのメリットとデメリットを考慮して、どこに実装するかを決めていきます。

データ構造を走査するコードの位置
実装箇所概要
データ構造のクラス階層このクラス階層がデータ構造を管理しているので、クラスの責任範囲が明確になりやすい
iterator操作クラスが走査をおこなわずにデータ要素クラスを呼び出すだけの場合に有効
操作クラスデータ構造のクラス階層から渡り歩くための情報を取得できる場合に利用できる。ただし、不要な情報まで公開しないことに注意

Visitorパターンの構造

 Visitorパターンの構成要素は、次のとおりです。

f:id:shimooka:20141208193244p:image

  • Visitorクラス

 操作のクラス階層で最上位に位置するクラスです。データ構造の具体的な要素であるConcreteElementクラスを処理するためのメソッドを用意します。ConcreteElementクラスはこのメソッドを呼び出すことで処理を依頼します。

 このメソッドは通常引数の型が異なるだけの同名メソッドオーバーロード)として実装されますが、PHP5では文法上許されていません。このため、メソッド内で引数の型を判断し、それぞれのConcreteElementクラス用の処理をおこなうことになります。

  • ConcreteVisitorクラス

 Visitorクラスのサブクラスです。Visitorクラスで宣言された処理用メソッドを実装します。

  • Elementクラス

 データ構造のクラス階層で最上位に位置するクラスです。Visitor型のオブジェクトを受け入れるためのメソッドを宣言します。このメソッド引数としてVisitor型のオブジェクトを受け取ります。

  • ConcreteElementクラス

 Elementクラスのサブクラスです。Elementクラスで宣言された受け入れ用メソッドを実装します。

  • ObjectStructureクラス

 Elementクラスの集合を表すクラスです。

Visitorパターンのメリット

 Visitorパターンのメリットとしては、以下のものが挙げられます。

  • 共通な操作を局所化する

 データ構造と操作を一緒にコーディングしてしまうと、コードが複雑化し拡張性も乏しくなってしまいます。Visitorパターンを適用することで、データ構造と操作が分離され、それぞれのコードが局所化されるため、よりシンプルなコードになりやすくなります。また、それぞれのクラスの部品化を促進することになります。

  • 新しい操作を簡単に追加できる

 Visitorパターンは、データ構造とデータ要素に対する操作を分離するパターンです。データ構造と操作は、具体的にはそれぞれElementクラスの階層とVisitorクラスの階層になります。Visitorパターンは、これらの階層どうしの結びつきをゆるくする働きがあります。このためデータ構造を変更することなく、新しいConcreteVisitorクラスを追加したり拡張できます

 一方、新しいデータ要素、つまりConcreteElementクラスを追加することは容易ではありません。例えば、サンプルコードに新しいデータ要素クラスであるDivisionクラスを追加する場合を考えてみましょう。これまで何度も出てきましたが、Visitorパターンはデータ構造と「データ要素に対する操作」を分離します。つまり、Visitorクラスに新しいデータ要素に対する操作を追加しなければならなくなります。

 このことから、Visitorパターンは「データ構造は変わらないが操作を色々変化させたい」という場合に向いていると言えるでしょう。

Visitorパターンの適用

 それではVisitorパターンの適用例を見てみましょう。

 ここでは組織と属する社員を表示するサンプルで、Compositeパターンの適用例にあるサンプルにVisitorパターンを適用したものになります。

 まずは、組織構造を形成するためのクラス群です。

 OrganizationEntryクラスは、オブジェクト構造に共通のAPIを定義し、組織や社員のコードと名前を格納しておくためのクラスです。ここでは抽象クラスとしており、後ほど出てくる組織クラスと社員クラスがサブクラスとなります。

 注目は訪問者を迎え入れるためのacceptメソッドです。受け取ったVisitor型のオブジェクトのvisitメソッドに自分自身を渡して、「自分自身に対する処理」をお願いしています。具体的にどの様な処理がおこなわれるかは、訪問者によって異なります。

OrganizationEntryクラス(OrganizationEntry.class.php
<?php
require_once 'Visitor.class.php';

/**
 * Componentクラスに相当する
 */
abstract class OrganizationEntry
{

    private $code;
    private $name;

    public function __construct($code, $name)
    {
        $this->code = $code;
        $this->name = $name;
    }

    public function getCode()
    {
        return $this->code;
    }

    public function getName()
    {
        return $this->name;
    }

    /**
     * 子要素を追加する
     * ここでは抽象メソッドとして用意
     */
    public abstract function add(OrganizationEntry $entry);

    /**
     * 子要素を取得する
     * ここでは抽象メソッドとして用意
     */
    public abstract function getChildren();

    /**
     * 組織ツリーを表示する
     * サンプルでは、デフォルトの実装を用意
     */
    public function accept(Visitor $visitor)
    {
        $visitor->visit($this);
    }
}

 次にOrganizationEntryクラスを継承した組織クラスと社員クラスです。この2クラスには、特に難しいところはないと思います。

Groupクラス(Group.class.php
<?php
require_once 'OrganizationEntry.class.php';

/**
 * Compositeクラスに相当する
 */
class Group extends OrganizationEntry
{

    private $entries;

    public function __construct($code, $name)
    {
        parent::__construct($code, $name);
        $this->entries = array();
    }

    /**
     * 子要素を追加する
     */
    public function add(OrganizationEntry $entry)
    {
        array_push($this->entries, $entry);
    }

    /**
     * 子要素を取得する
     */
    public function getChildren()
    {
        return $this->entries;
    }

}
Employeeクラス(Employee.class.php
<?php
require_once 'OrganizationEntry.class.php';

/**
 * Leafクラスに相当する
 */
class Employee extends OrganizationEntry
{

    public function __construct($code, $name)
    {
        parent::__construct($code, $name);
    }

    /**
     * 子要素を追加する
     * Leafクラスは子要素を持たないので、例外を発生させている
     */
    public function add(OrganizationEntry $entry)
    {
        throw new Exception('method not allowed');
    }

    public function getChildren()
    {
        return array();
    }
}

 続けて訪問者側のクラス群を見ていきましょう。

 まずは、訪問者の共通APIを定義するVisitorインターフェースです。唯一のメソッドvisitが用意されており、引数として組織構造であるOrganizationEntry型のオブジェクトを受け取るようになっています。

Visitorクラス(Visitor.class.php
<?php
require_once 'OrganizationEntry.class.php';

interface Visitor {
    public function visit(OrganizationEntry $entry);
}

 次はConcreteVisitorクラスに相当する具体的な訪問者クラスです。ここでは2種類の訪問者を用意しました。また、データ構造を走査するコードは訪問者に記述しています。

 1つ目は組織や社員のコードと名前を表示するDumpVisitorクラスです。

 visitメソッドを見てください。まず、渡されたOrganizationEntry型のオブジェクト自身の情報を表示します。続いて、ぶら下がっている組織や社員を再帰的に訪問(visit)していることが分かると思います。

DumpVisitorクラス(DumpVisitor.class.php
<?php
require_once 'Visitor.class.php';

class DumpVisitor implements Visitor
{
    public function visit(OrganizationEntry $entry)
    {
        if (get_class($entry) === 'Group') {
            echo '';
        } else {
            echo '&nbsp;&nbsp;';
        }
        echo $entry->getCode() . ":" . $entry->getName() . "<br>\n";

        foreach ($entry->getChildren() as $ent) {
            $ent->accept($this);
        }
    }
}

 2つ目は組織や社員の数をカウントするCountVisitorクラスです。

 visitメソッドを見みると、先のDumpVisitorクラスのvisitメソッドと似ていることが分かると思います。こちらも渡された組織構造を再帰的に訪問し、組織と社員の数を別々にカウントしています。カウントした結果は、getGroupCountメソッドとgetEmployeeCountメソッドでそれぞれ取得できます。

CountVisitorクラス(CountVisitor.class.php
<?php
require_once 'Visitor.class.php';

class CountVisitor implements Visitor
{
    private $group_count = 0;
    private $employee_count = 0;

    public function visit(OrganizationEntry $entry)
    {
        if (get_class($entry) === 'Group') {
            $this->group_count++;
        } else {
            $this->employee_count++;
        }
        foreach ($entry->getChildren() as $ent) {
            $this->visit($ent);
        }
    }

    public function getGroupCount()
    {
        return $this->group_count;
    }

    public function getEmployeeCount()
    {
        return $this->employee_count;
    }
}

 ここまで説明してきたクラスを利用する側のコードも見ておきましょう。

 最初に組織構造を作成している部分は、Compositeパターンにあるものと同じです。しかし、組織構造を処理するときに処理をおこなうオブジェクト(Visitor型のオブジェクト)を渡しているところが特徴的です。

 このように、オブジェクト構造を修正することなく、Visitor型のオブジェクトを変えるだけでさまざまな処理をおこなうことができます。

クライアント側コード(visitor_client.php
<?php
require_once 'Group.class.php';
require_once 'Employee.class.php';
require_once 'DumpVisitor.class.php';
require_once 'CountVisitor.class.php';

/**
 * 木構造を作成
 */
$root_entry = new Group("001", "本社");
$root_entry->add(new Employee("00101", "CEO"));
$root_entry->add(new Employee("00102", "CTO"));

$group1 = new Group("010", "○○支店");
$group1->add(new Employee("01001", "支店長"));
$group1->add(new Employee("01002", "佐々木"));
$group1->add(new Employee("01003", "鈴木"));
$group1->add(new Employee("01003", "吉田"));

$group2 = new Group("110", "△△営業所");
$group2->add(new Employee("11001", "川村"));
$group1->add($group2);
$root_entry->add($group1);

$group3 = new Group("020", "××支店");
$group3->add(new Employee("02001", "萩原"));
$group3->add(new Employee("02002", "田島"));
$group3->add(new Employee("02002", "白井"));
$root_entry->add($group3);

/**
 * 木構造をダンプ
 */
$root_entry->accept(new DumpVisitor());

/**
 * 同じ木構造に対して、別のVisitorを使用する
 */
$visitor = new CountVisitor();
$root_entry->accept($visitor);
echo '組織数:' . $visitor->getGroupCount() . '<br>';
echo '社員数:' . $visitor->getEmployeeCount() . '<br>';

 ここで、データ構造を渡り歩く訪問者の様子を示しておきます。話を単純にするため、組織は2階層(Group1とGroup2)で2階層目の組織に社員(Employee)が属している場合の例になっています。

f:id:shimooka:20141208193330p:image

 こう見ると、GroupオブジェクトもEmployeeオブジェクトも、Visitorオブジェクトacceptメソッドで受け入れ、Visitorオブジェクトのvisitメソッドに自分自身を渡して処理してもらっている様子が分かりますね。

 最後に、サンプルコードのクラス図を示します。

f:id:shimooka:20141208193242p:image

適用例の実行結果

 Visitorパターンを適用したサンプルの実行結果は、次のようになります。

f:id:shimooka:20141208193243p:image

Visitorパターンのオブジェクト指向的要素

 Visitorパターンの特徴的な要素に「ダブルディスパッチ(double dispatch)」が挙げられます。ここでは、このダブルディスパッチについて見ていきましょう。

 まず、ダブルディスパッチとは何でしょうか?簡単には「実行される処理が2つの型に依存すること」と言えます。

 Visitorパターンの場合、「2つの型」とはConcreteVisitorとConcreteElementを指します。Visitorパターンでは、Elementのacceptメソッドと、与えられたVisitorのvisitメソッドによって実行される処理が確定します。このacceptメソッドとvisitメソッドの2つに対して要求が送信される(dispatchされる)ことから、ダブルディスパッチと呼ばれます。

関連するパターン

 データ構造にCompositeパターンが適用されることがあります。サンプルコードでは、データ構造にCompositeパターンが適用されています。

 Visitorパターンと同様、あるデータ構造に対して操作をおこなうパターンです。

 Visitorパターンはデータ構造の要素に対して操作をおこなうパターンですが、Iteratorパターンはデータ構造の要素を1つずつ取得するために利用されるパターンです。

まとめ

 ここではデータ構造から分離された「操作」がデータ構造を渡り歩いて処理をおこなうVisitorパターンを見てきました。

[][]PHPによるデザインパターン入門 - Strategy〜戦略を切り替える

このエントリは、Do You PHP?(www.doyouphp.jp)で公開していたコンテンツを移行/加筆/修正したものです。公開の経緯はこちらをどうぞ。目次はこちらです。サンプルコードを手直ししたものgithubに上げてありますのでそちらもどうぞ。

GoF本における分類

 振る舞い+オブジェクト

はじめに

 ここではStrategyパターンについて見ていきましょう。

 strategyという単語は「戦略」「作戦」「方針」「方策」などの意味があります。

 Strategyパターンは、この「作戦」や「戦略」を1つのクラスにまとめ、「作戦」や「戦略」の単位で切り替えられるようにするパターンです。

たとえば

 CSVファイルもしくはXMLファイルを読み込んでデータ処理をおこなう場合を考えてみましょう。いずれの場合も、処理フローは次のようになります。

  1. データファイルを読み込む
  2. 読み込んだデータを処理する

 しかし、「データファイルを読み込む」部分は、データファイルがCSVファイルなのかXMLファイルなのかによって読み込み時の処理が異なることになります。

 そこで、これらの読み込み処理を同じクラスやメソッドとして作成することを考えてみましょう。どうでしょうか?あまり良い方法ではなさそうですね。

 同じクラスやメソッドとして作成してしまうと、当然ですが質の異なる処理が混ざることになります。その結果、保守性や再利用性が損なわれることになります。たとえば、新しいデータフォーマットに対応するためには、そのクラスやメソッドそのものを修正する必要が発生してしまう、といった具合です。

 「データファイルを読み込む」という処理は非常に一般的なものです。データフォーマットごとの処理をまとめられるとコードをシンプルに保つことができますし、他のアプリケーションでも再利用できそうです。

 あとは、どうやってデータフォーマットごとの処理クラスを切り替えられるようにするか?

 ここで、Strategyパターンの登場です。

Strategyパターンとは?

 Strategy パターンはオブジェクトの振る舞いに注目したパターンで、アルゴリズムをクラスとして定義し、切り替えられるようにすることを目的としています。

 GoF本では、Strategyパターンの目的は次のように定義されています。

アルゴリズムの集合を定義し、各アルゴリズムカプセル化して、それらを交換可能にする。Strategyパターンを利用することで、アルゴリズムを、それを利用するクライアントからは独立に変更することができるようになる。

 Strategyパターンでは、それぞれの処理をクラスとして定義します。その際、クライアントアクセスさせるための共通APIを用意しておくのがポイントです。これにより、処理クラスを利用する側は具体的な実装を意識することなく、共通のAPIで処理を実行できます。

 また、処理の実行を処理クラスのオブジェクトに委譲することで、処理の切り替えができるようにしています。

Strategyパターンの構造

 Strategyパターンのクラス図と構成要素は、次のとおりです。

f:id:shimooka:20141208193138p:image

  • Strategyクラス

 それぞれの処理に共通のAPIを定義します。Contextクラスからは、Strategyクラスで定義されたAPIを通じて、ConcreteStrategyクラスで提供される具体的な処理を呼び出します。

  • ConcreteStrategyクラス

 Strategyクラスのサブクラスで、Strategyクラスで定義されたAPIを実装したクラスです。このクラスに具体的な処理内容を記述します。

  • Contextクラス

 Strategy型のオブジェクトを内部に保持し、具体的な処理をそのオブジェクトに委譲します。こうすることで、ConcreteStrategyクラスに依存することがなくなりますので、ConcreteStrategyクラスを切り替えることができます

Strategyパターンのメリット

 Strategyパターンのメリットとしては、以下のものが挙げられます。

  • 処理毎にまとめることができる

 それぞれの処理がクラスにまとめられて実装されており、コードは処理内容に専念することができます。これにより、保守性が高まります。

 また、新しい処理が追加された場合も、既存のコードに手を入れることなく、新しいクラスを作成するだけで済みます。

  • 異なる処理を選択するための条件文がなくなる

 1つのクラスやメソッドに異なる処理を記述した場合、if文やswitch文を使って処理を分岐することになります。これは、コードの可読性を落とすため、保守性・拡張性が下がります。Strategyパターンを適用すると、処理がクラス単位にまとめて実装されます。この結果、if文やswitch文を使うことがなくなり、非常にすっきりしたコードになります。

  • 異なる処理を動的に切り替えることができる

 クラス単位に処理がまとめて実装されているので、クライアントは使いたいConcreteStrategyクラスのインスタンスをContextオブジェクトに渡すだけで、処理を動的に切り替えることができます。

Strategyパターンの適用

 ここでStrategyパターンの適用例を見てみることにしましょう。

 フォーマットの異なる商品データを読み込み、それを一覧表示するサンプルアプリケーションを用意しました。データファイルは固定長とタブ区切りの2種類で、データレイアウトと用意したデータファイルは、それぞれ次の通りです。

固定長データのデータレイアウト
項目開始位置終了位置備考
商品名120
商品番号2130
価格3138カンマなし
発売日39-YYYYMMDD形式
固定長データ(fixed_length_data.txt)

商品名 商品番号 価格 発売日

限定Tシャツ ABC0001 3800 20060311

ぬいぐるみ ABC0002 1500 20051201

クッキーセット ABC0003 800 20060520

タブ区切りデータのデータレイアウト
項目備考
商品名
商品番号
価格カンマなし
発売日YYYY/MM/DD形式(先頭ゼロなし)
タブ区切りデータ(tab_separated_data.txt)

商品番号 商品名 価格 発売日

ABC0001 限定Tシャツ 3800 2006/3/11

ABC0002 ぬいぐるみ 1500 2005/12/1

ABC0003 クッキーセット 800 2006/5/20

 最初はReadItemDataStrategyクラスです。Strategyクラスに相当し、抽象クラスとして定義しています。

 2つのメソッドgetDataとreadDataが定義されていますが、getDataメソッドの方はcontextクラスに相当するItemDataContextクラスに公開されるメソッドです。もう一方のgetItemDataメソッドは抽象メソッドとなっており、サブクラスで具体的な読み込み処理を実装します。

ReadItemDataStrategyクラス(ReadItemDataStrategy.class.php
<?php
/**
 * Strategyに相当する
 */
abstract class ReadItemDataStrategy
{

    private $filename;

    /**
     * コンストラクタ
     */
    public function __construct($filename)
    {
        $this->filename = $filename;
    }

    /**
     * データファイルを読み込み、オブジェクトの配列で返す
     * Contextに提供するメソッド
     * @param string データファイル名
     * @return データオブジェクトの配列
     */
    public function getData()
    {
        if (!is_readable($this->getFilename())) {
            throw new Exception('file [' . $this->getFilename() . '] is not readable !');
        }

        return $this->readData($this->getFilename());
    }

    /**
     * ファイル名を返す
     * @return ファイル名
     */
    public function getFilename()
    {
        return $this->filename;
    }

    /**
     * ConcreteStrategyクラスに実装させるメソッド
     * @param string データファイル名
     * @return データオブジェクトの配列
     */
    protected abstract function readData($filename);
}

 次は、ReadItemDataStrategyクラスのサブクラスたちです。

 先ほどの説明の通り、データ形式ごとにクラスを作成しています。ReadFixedLengthDataStrategyクラスは固定長データを、ReadTabSeparatedDataStrategyクラスはタブ区切りデータをそれぞれ読み込みます。なお、読み込むファイル名は、コンストラクタで指定します。

ReadFixedLengthDataStrategyクラス(ReadFixedLengthDataStrategy.class.php
<?php
require_once 'ReadItemDataStrategy.class.php';

/**
 * 固定長データを読み込む
 * ConcreteStrategyに相当する
 */
class ReadFixedLengthDataStrategy extends ReadItemDataStrategy
{

    /**
     * データファイルを読み込み、オブジェクトの配列で返す
     * @param string データファイル名
     * @return データオブジェクトの配列
     */
    protected function readData($filename)
    {
        $fp = fopen($filename, 'r');

        /**
         * ヘッダ行を抜く
         */
        $dummy = fgets($fp, 4096);

        /**
         * データの読み込み
         */
        $return_value = array();
        while ($buffer = fgets($fp, 4096)) {
            $item_name = trim(substr($buffer, 0, 20));
            $item_code = trim(substr($buffer, 20, 10));
            $price = (int)substr($buffer, 30, 8);
            $release_date = substr($buffer, 38);

            /**
             * 戻り値のオブジェクトの作成
             */
            $obj = new stdClass();
            $obj->item_name = $item_name;
            $obj->item_code = $item_code;
            $obj->price = $price;

            $obj->release_date = mktime(0, 0, 0,
                                        substr($release_date, 4, 2),
                                        substr($release_date, 6, 2),
                                        substr($release_date, 0, 4));

            $return_value[] = $obj;
        }

        fclose($fp);

        return $return_value;
    }
}
ReadTabSeparatedDataStrategyクラス(ReadTabSeparatedDataStrategy.class.php
<?php
require_once 'ReadItemDataStrategy.class.php';

/**
 * タブ区切りデータを読み込む
 * ConcreteStrategyに相当する
 */
class ReadTabSeparatedDataStrategy extends ReadItemDataStrategy
{

    /**
     * データファイルを読み込み、オブジェクトの配列で返す
     * @param string データファイル名
     * @return データオブジェクトの配列
     */
    protected function readData($filename)
    {
        $fp = fopen($filename, 'r');

        /**
         * ヘッダ行を抜く
         */
        $dummy = fgets($fp, 4096);

        /**
         * データの読み込み
         */
        $return_value = array();
        while ($buffer = fgets($fp, 4096)) {
            list($item_code, $item_name, $price, $release_date) = split("\t", $buffer);

            /**
             * 戻り値のオブジェクトの作成
             */
            $obj = new stdClass();
            $obj->item_name = $item_name;
            $obj->item_code = $item_code;
            $obj->price = $price;

            list($year, $mon, $day) = split('/', $release_date);
            $obj->release_date = mktime(0, 0, 0,
                                        $mon,
                                        $day,
                                        $year);

            $return_value[] = $obj;
        }

        fclose($fp);

        return $return_value;
    }
}

 続いて、Contextクラスに相当するクラス、ItemDataContextクラスを見てみましょう。

 ItemDataContextクラスの特徴は、コンストラクタにReadItemDataStrategy型のオブジェクトを受け取り、getItemDataメソッドで具体的な処理を委譲している部分です。つまり、コンストラクタに引き渡すReadItemDataStrategy型のオブジェクトを変更するだけで、getItemDataメソッドの動作を変更できます

ItemDataContextクラス(ItemDataContext.class.php
<?php
/**
 * Contextに相当する
 */
class ItemDataContext
{

    private $strategy;

    /**
     * コンストラクタ
     * @param ReadItemDataStrategy ReadItemDataStrategyオブジェクト
     */
    public function __construct(ReadItemDataStrategy $strategy)
    {
        $this->strategy = $strategy;
    }

    /**
     * 商品情報をオブジェクトの配列で返す
     * @return データオブジェクトの配列
     */
    public function getItemData()
    {
        return $this->strategy->getData();
    }

}

 最後にクライアントのコードを確認しておきましょう。

 単純にそれぞれのデータファイルを読み込んで一覧表示する処理をおこなっているだけですが、いかがでしょうか?実際にインスタンス化しているReadItemDataStrategyオブジェクトの指定以外は全く同じですね。

クライアント側コード(strategy_client.php
<?php
require_once 'ItemDataContext.class.php';
require_once 'ItemDataContextByName.class.php';
require_once 'ReadFixedLengthDataStrategy.class.php';
require_once 'ReadTabSeparatedDataStrategy.class.php';

function dumpData($data) {
    echo '<dl>';
    foreach ($data as $object) {
        echo '<dt>' . $object->item_name . '</dt>';
        echo '<dd>商品番号:' . $object->item_code . '</dd>';
        echo '<dd>\\' . number_format($object->price) . '-</dd>';
        echo '<dd>' . date('Y/m/d', $object->release_date) . '発売</dd>';
    }
    echo '</dl>';
}

/**
 * 固定長データを読み込む
 */
$strategy1 = new ReadFixedLengthDataStrategy('fixed_length_data.txt');
$context1 = new ItemDataContext($strategy1);
dumpData($context1->getItemData());

echo '<hr>';

/**
 * タブ区切りデータを読み込む
 */
$strategy2 = new ReadTabSeparatedDataStrategy('tab_separated_data.txt');
$context2 = new ItemDataContext($strategy2);
dumpData($context2->getItemData());

 また、サンプルコードのクラス図は次のようになります。

f:id:shimooka:20141208193136p:image

適用例の実行結果

 Strategyパターンを適用したサンプルの実行結果は、次のようになります。

f:id:shimooka:20141208193137p:image

Strategyパターンのオブジェクト指向的要素

 Strategyパターンは「継承」と「ポリモーフィズム」を活用しているパターンです。

 StrategyクラスとConcreteStrategyクラスは、継承の関係にあります。親クラスであるStrategyクラスで処理内容が変わる部分を抽象メソッドとして定義します。一方、サブクラスであるConcreteStrategyクラスでは、抽象メソッドを実装し、具体的な処理を記述します。こうすることで、同じAPIを持ち、かつ具体的な処理が異なるクラス群を用意できます

 また、Contextクラスは、Strategy型のインスタンスを内部に保持します。このインスタンスは、具体的にはStrategyクラスを継承したサブクラスインスタンスです。Contextクラスは、クライアントからの処理要求を受け取ると、保持したインスタンスに具体的な処理を委譲します。この時、処理を委譲する部分を、処理側の親クラスであるStrategyクラスのAPIだけを使ってプログラミングを行っておくことがポイントです。こうすることで、Strategy型のインスタンスがどの様な処理を行うものであれ、正しく動作することになります。

 この結果、ConcreteStrategyクラスを簡単に差し替えたり、追加したりできるのです。Strategyパターンは、委譲を使って処理内容全体を切り替えるパターンと言えます。

 なお、このような処理を切り替えるパターンとしては、Strategyパターン以外にTemplate Methodパターンがあります。Template Methodパターンでは、継承を使って処理内容の一部を切り替えています。

関連するパターン

 ConcreteStrategyクラスのインスタンスは、Flyweightパターンを使って共有できる場合があります。

まとめ

 ここではアルゴリズムをクラスにまとめ、そのアルゴリズムごとに切り替えできるようにするStrategyパターンを見てきました。

[][]PHPによるデザインパターン入門 - State〜状態を表す

このエントリは、Do You PHP?(www.doyouphp.jp)で公開していたコンテンツを移行/加筆/修正したものです。公開の経緯はこちらをどうぞ。目次はこちらです。サンプルコードを手直ししたものgithubに上げてありますのでそちらもどうぞ。

GoF本における分類

 振る舞い+オブジェクト

はじめに

 ここではStateパターンについて見ていきましょう。

 stateという単語は「状態」の意味がありますが、Stateパターンは物ではなく「状態」をクラスとして表現し、「状態」ごとに振る舞いを切り替えられるようにするパターンです。

たとえば

 たとえば、部屋の照明を考えてみましょう。照明には、点灯している状態(オン)と消灯している状態(オフ)の2つの状態があることになります。照明の状態がオンの場合、当然ですが照明が灯っている、つまり「明かりが灯る」という動作をしていると言えます。逆にオフの場合は「明かりが消える」という動作をしているということになります。このように「状態」によって振る舞いが変わるものはよくあります。

 アプリケーションでも何らかの状態によって振る舞いが変わるものがあります。

 たとえば、認証機能を持つアプリケーションの場合、認証済みの状態と認証していない状態が存在します。また、その状態によってメニュー表示を変えたり、特定の機能の動作を変えたりすることがあります。

 このような場合、状態に依存する処理をどのように記述すれば良いでしょうか?おそらく、if文やswitch文を使って実装する場合が多いと思います。先の認証機能の例ですと状態は2種類しかありませんのでさほど問題にはならないかもしれません。しかし、状態の種類が多くなるとコードの可読性が落ち、保守性・拡張性を落とす原因になってしまいます。

 また、状態に関するコードがあちこちに散らばってしまうことになります。このため、新しい状態を追加することが困難になります。

 Stateパターンは、「状態」と「状態による振る舞い」を1つのクラスにまとめることで、先のような問題を解決します。

Stateパターンとは?

 Stateパターンはオブジェクトの振る舞いに注目したパターンで、その名の通り「状態」をクラスとして表すことを目的としています

 GoF本では、Stateパターンの目的は次のように定義されています。

オブジェクトの内部状態が変化したときに、オブジェクトが振る舞いを変えるようにする。クラス内では振る舞いの変化を記述せず、状態を表すオブジェクトを導入することでこれを実現する。

 Stateパターンでは、それぞれの具体的な状態をクラスとして定義します。これらの状態クラスは「どの状態か」を意識することなくアクセスできるよう共通のAPIを持っています。また、クライアントからアクセスさせるための処理クラスを用意し、その内部に状態クラスのインスタンスを保持します。この処理クラスは、クライアントからの要求を受け取った後、内部に保持した状態オブジェクトに実際の処理を任せています。

 こうすることで、状態による振る舞いの変化を実現しています。

Stateパターンの構造

 Stateパターンのクラス図と構成要素は、次のとおりです。

f:id:shimooka:20141208192739p:image

  • Stateクラス

 それぞれの状態に共通のAPIを定義します。このAPIは、状態固有の振る舞いをおこなうメソッドになります。Contextクラスからは、Stateクラスで定義されたAPIを通じて、ConcreteStateクラスで実装された具体的な処理を呼び出します。

  • ConcreteStateクラス

 Stateクラスのサブクラスで、Stateクラスで定義されたAPIを実装したクラスです。このクラスに、状態ごとの具体的な処理内容を記述します。

  • Contextクラス

 State型のオブジェクトを内部に保持し、具体的な処理をそのオブジェクトに委譲します。これにより、ConcreteStateクラスに依存することがなくなり、ConcreteStateクラスを簡単に切り替えることができます。

Stateパターンのメリット

 Stateパターンのメリットとしては、以下のものが挙げられます。

  • 状態に固有の処理をまとめることができる

 Stateパターンを適用すると、それぞれの状態に固有な処理がクラスにまとめて実装されますので、コードを記述する際、その状態に固有な処理に専念できます。これにより、保守性が高まります。

 また、新しい状態が追加された場合も、新しい状態クラスを作成するだけで済みます。

  • 状態に固有の処理を選択するための条件文がなくなる

 状態によって振る舞いを変える場合、1つのクラスやメソッドに処理を記述したくなってしまいます。しかし、クラスやメソッドごとにif文やswitch文を使って処理を分岐することになってしまい、コードの可読性を落としてしまいます。また、保守性・拡張性が下がることにもつながります。Stateパターンを適用すると、状態ごとの処理がクラス単位にまとめて実装されますので、if文やswitch文を使うことがなくなり、非常にすっきりしたコードになります。

Stateパターンの適用

 Stateパターンの適用例を見てみましょう。

 ここでは簡単な認証機能にStateパターンを適用した例に取り上げます。

 このアプリケーションには、認証機能のほか、簡単なカウンタ機能があります。このカウンタ機能はログインした状態の場合のみ利用できます。また、状態としては認証済み(ログイン)、未認証(ログアウト)の2つがあります。

 では、早速コードを見ていきましょう。

 まずは、Contextクラスに相当するUserクラスです。クライアントはこのUserクラスを利用しますが、その動作は内部に保持している状態オブジェクトによって変化します。

Userクラス(User.class.php
<?php
require_once 'UnauthorizedState.class.php';

/**
 * Contextクラスに相当する
 */
class User
{

    private $name;
    private $state;
    private $count = 0;

    public function __construct($name)
    {
        $this->name = $name;

        // 初期値
        $this->state = UnauthorizedState::getInstance();
        $this->resetCount();
    }

    /**
     * 状態を切り替える
     */
    public function switchState()
    {
        echo "状態遷移:" . get_class($this->state) . "";
        $this->state = $this->state->nextState();
        echo get_class($this->state) . "<br>";
        $this->resetCount();
    }

    public function isAuthenticated()
    {
        return $this->state->isAuthenticated();
    }

    public function getMenu()
    {
        return $this->state->getMenu();
    }

    public function getUserName()
    {
        return $this->name;
    }

    public function getCount()
    {
        return $this->count;
    }

    public function incrementCount()
    {
        $this->count++;
    }

    public function resetCount()
    {
        $this->count = 0;
    }
}

 また、状態を切り替えるためのメソッドswitchStateが定義されています。このメソッドの中に状態を切り替えるためのコードが記述されていますが、

$this->state = $this->state->nextState();

 といった具合に内部に保持している状態オブジェクトのnextStateメソッドを呼び出しているだけです。状態オブジェクトには2種類あると説明しましたが、どの状態かを意識することなくnextStateメソッドを呼び出しています。この詳細の説明は、状態クラスの説明のときにおこないますので、ちょっと待っていてください。

 次に状態を表すクラスたちを見ていきましょう。

 まずは状態クラスに共通のAPIを定義しているUserStateインターフェースです。Stateクラスに相当します。

 それぞれの具体的な状態クラスは、このインターフェースを実装することになります。

UserStateインターフェース(UserState.class.php
<?php
/**
 * Stateクラスに相当する
 * 状態毎の動作・振る舞いを定義する
 */
interface UserState {
    public function isAuthenticated();
    public function nextState();
    public function getMenu();
}

 このインターフェースには3つのメソッドが定義されています。認証されているかどうかを返すisAuthenticatedメソッド、状態を切り替えるnextStateメソッド、その状態で利用可能なメニュー一覧を返すgetMenuメソッドです。

 次は具体的な状態を表すクラスです。ConcreteStateクラスに相当します。

 認証済みの状態を表すAuthorizedStateクラスと未認証の状態を表すUnauthorizedStateクラスの2クラスです。

AuthorizedStateクラス(AuthorizedState.class.php
<?php
require_once 'UserState.class.php';
require_once 'UnauthorizedState.class.php';

/**
 * ConcreteStateクラスに相当する
 * 認証後の状態を表すクラス
 */
class AuthorizedState implements UserState
{

    private static $singleton = null;

    private function __construct()
    {
    }

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

    public function isAuthenticated()
    {
        return true;
    }

    public function nextState()   
    {
        // 次の状態(未認証)を返す
        return UnauthorizedState::getInstance();
    }

    public function getMenu()
    {
        $menu = '<a href="?mode=inc">カウントアップ</a> | '
              .    '<a href="?mode=reset">リセット</a> | '
              .    '<a href="?mode=state">ログアウト</a>';
        return $menu;
    }

    /**
     * このインスタンスの複製を許可しないようにする
     * @throws RuntimeException
     */
    public final function __clone() {
        throw new RuntimeException ('Clone is not allowed against ' . get_class($this));
    }
}
UnauthorizedStateクラス(UnauthorizedState.class.php
<?php
require_once 'UserState.class.php';
require_once 'AuthorizedState.class.php';

/**
 * ConcreteStateクラスに相当する
 * 未認証の状態を表すクラス
 */
class UnauthorizedState implements UserState
{
    private static $singleton = null;

    private function __construct()
    {
    }

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

    public function isAuthenticated()
    {
        return false;
    }

    public function nextState()
    {
        // 次の状態(認証)を返す
        return AuthorizedState::getInstance();
    }

    public function getMenu()
    {
        $menu = '<a href="?mode=state">ログイン</a>';
        return $menu;
    }

    /**
     * このインスタンスの複製を許可しないようにする
     * @throws RuntimeException
     */
    public final function __clone() {
        throw new RuntimeException ('Clone is not allowed against ' . get_class($this));
    }
}

 これらのクラスには、Singletonパターンも併せて適用されています。

 注目していただきたいメソッドはnextStateメソッドです。このメソッドは次の状態に切り替えるためのメソッドですが、UnauthorizedStateクラスではAuthorizedStateクラスのインスタンス、AuthorizedStateクラスではUnauthorizedStateクラスのインスタンスを返すようになっています。つまり、「未認証」の次の状態は「認証済み」、「認証済み」の次の状態は「未認証」という状態の遷移を表しています。

 Userクラスの内部では、このnextStateメソッドを使って状態の切り替えをおこなっていましたが、その際「現在どの状態なのか」を意識しないでnextStateメソッドを呼び出していたと思います。これは、状態クラスであるAuthorizedStateクラスやUnauthorizedStateクラス自身が次に遷移すべき状態を知っているからこそ可能になっています。

 なお、新しい状態を増やす場合、AuthorizedStateクラスやUnauthorizedStateクラスと同様にUserStateインターフェースを実装したクラスを用意するだけで済みます。

 最後にクライアントのコードです。このコードには、状態に関連するコードが一切出てきていないことを確認してください。

クライアント側コード(state_client.php
<?php
require_once 'User.class.php';

session_start();

$context = isset($_SESSION['context']) ? $_SESSION['context'] : null;
if (is_null($context)) {
    $context = new User('ほげ');
}

$mode = (isset($_GET['mode']) ? $_GET['mode'] : '');
switch ($mode) {
    case 'state':
        echo '<p style="color: #aa0000">状態を遷移します</p>';
        $context->switchState();
        break;
    case 'inc':
        echo '<p style="color: #008800">カウントアップします</p>';
        $context->incrementCount();
        break;
    case 'reset':
        echo '<p style="color: #008800">カウントをリセットします</p>';
        $context->resetCount();
        break;
}

$_SESSION['context'] = $context;

echo 'ようこそ、' . $context->getUserName() . 'さん<br>';
echo '現在、ログインして' . ($context->isAuthenticated() ? 'います' : 'いません') . '<br>';
echo '現在のカウント:' . $context->getCount() . '<br>';
echo $context->getMenu() . '<br>';

 このサンプルアプリケーションのクラス図は、次のようになります。

f:id:shimooka:20141208192734p:image

適用例の実行結果

 Stateパターンを適用したサンプルの実行結果です。まずは、初期画面です。

f:id:shimooka:20141208192738p:image

ログイン」リンクをクリックするとすると次のようになります。

f:id:shimooka:20141208192735p:image

この後、「カウントアップ」リンクを3回クリックすると次のようになります。

f:id:shimooka:20141208192736p:image

最後に、「ログアウト」リンクをクリックすると次のようになります。

f:id:shimooka:20141208192737p:image

Stateパターンのオブジェクト指向的要素

 Stateパターンは「ポリモーフィズム」を活用しているパターンです。

 Contextクラスは、State型のインスタンスを内部に保持します。このインスタンスは、具体的にはStateクラスを継承もしくは実装したConcreteStateクラスのインスタンスです。一方、Contextクラスは、クライアントから受け取った処理要求を、保持しているStateインスタンスに処理を委譲します。Contextクラス自身は状態に関する処理を一切行いません。この結果、保持したインスタンスが具体的にどのConcreteStateクラスのインスタンスなのかを意識することなく、振る舞いを変更することができるのです。併せて、ConcreteStateクラスを簡単に差し替えたり、追加したりできるのです。Stateパターンは、委譲を使って状態固有の処理を切り替えるパターンと言えます。

 また、StateパターンとStrategyパターンは非常によく似たパターンですが、用いる場面は全く異なります。Stateパターンは状態によって振る舞いを変えますが、Strategyパターンは処理する対象の違いによって振る舞いを変えます

関連するパターン

 Singletonパターンは、ConcreteStateクラスに適用できることが多いパターンです。

 Singletonパターンと同様、ConcreteStateクラスにFlyweightパターンを適用できる場合があります。

まとめ

 ここでは「状態」と「状態による振る舞い」を1つのクラスにまとめるStateパターンについて見てきました。

2014-12-18

[][]PHPによるデザインパターン入門 - Proxy〜具体的な実装を隠す身代わり

このエントリは、Do You PHP?(www.doyouphp.jp)で公開していたコンテンツを移行/加筆/修正したものです。公開の経緯はこちらをどうぞ。目次はこちらです。サンプルコードを手直ししたものgithubに上げてありますのでそちらもどうぞ。

GoF本における分類

 構造+オブジェクト

はじめに

 ここではProxyパターンについて説明します。

 Windowsには「ショートカット」という機能があります。たとえばブラウザショートカットデスクトップ上などに登録しておき、実行したい時にそのショートカットダブルクリックすることで、ショートカットの実体であるブラウザが実行されます。他のアプリケーションやテキストファイルでも同様の動作をおこないます。このように「実際は本物の身代わりだが、本物にアクセスしているかのように振る舞うもの」、それがプロキシです。

 Proxyパターンもこれと同様で、身代わりとなるオブジェクトを通じて間接的に目的のオブジェクトにアクセスさせるためのパターンです。

たとえば

 データベースを使ったシステムを考えてみましょう。

 データベースを使ったシステムには、当然ですが何らのデータベースが必要になりますね。しかし、データベースを使ったシステムを開発する場合、肝心のデータベースがないとプログラムを作成できない、もしくはテストできないといった状況になりがちです。

 なぜなら、システムを成すプログラムコードにデータベースに関するコードが記述されている、つまりビジネスロジックを含むプログラムデータベースが強く結びついてしまっているからです。「データベースを使ったシステム」なので当然といえば当然ですが、ビジネスロジックは本来データベースとは関係がありません。しかし、ビジネスロジックデータベースに関連する処理をまとめて書いてしまうことで、データベースと強く結びつきすぎてしまうことが問題なのです。

 Webシステムのような変化の激しいアプリケーションでは、変化への素早い対応が求められることが多く、ビジネス上の重要なポイントになっています。これを踏まえると、システムの設計・開発段階から何らか考慮しておくことがシステム構築の鍵を握っているとも言えます。

 また、データベースに関連する処理は比較的コストのかかる処理となります。このため、更新されないデータをキャッシュしたり、データベースとの接続やデータ取得のタイミングの調整が必要になる場合があります。

 これらの問題を回避するには、どうしたら良いでしょうか?

 ここで、データベースに関連する部分と関連しない部分を分けて*1、その間にクッション役のクラスを用意してやります。そうすると、データベースに関連する部分と関連しない部分のやりとりが間接的になりますね。間接的になるということは、具体的なクラスに依存しないコードになるということです。

 では、このクッションに色々な役割を持たせてやると、どうなるでしょうか…何となく想像できますか?

 たとえば、このクッションがデータベースにアクセスしているかのような振る舞いをするとしたら?データベースに関連しない部分のコードはデータベースがなくても開発できるようになりますね*2

 もうひとつ。このクッションがデータベースから取得したデータをキャッシュする機能を持っていたら…もうお分かりですね。

 このように、やりとりをするオブジェクトの間にクッション役を用意し、このクッションに色々な役割を持たせるパターン…これがProxyパターンです。

Proxyパターンとは?

 Proxyパターンはオブジェクトの構造に注目したパターンです。Proxyパターンは、オブジェクトとその利用側との間に緩衝剤を用意することで、お互いを分離したり付加的な機能を追加することを目的としています

 GoF本では、Proxyパターンの目的は次のように定義されています。

あるオブジェクトへのアクセスを制御するために、そのオブジェクトの代理、または入れ物を提供する。

 みなさんは「プロキシサーバ」という言葉を聞いたことがあるかもしれません。プロキシサーバキャッシュサーバとも呼ばれます)は、外部ネットワークとのアクセス制御やコンテンツのキャッシング、さらにはインターネット上での匿名性を高めるために利用されるサーバです。

 代理オブジェクトの利用側もプロキシサーバの場合と同様、代理オブジェクトにアクセスしているのか目的のオブジェクトにアクセスしているのかを意識させないような構造をしています。具体的には、代理オブジェクトと目的のオブジェクトが共通のインターフェースを実装する事で実現されています。

 また、代理オブジェクトは目的のオブジェクトを内部に保持して、具体的な処理を目的のオブジェクトに転送します。

Proxyパターンの構造

 Proxyパターンのクラス図と構成要素は、次のようになります。

f:id:shimooka:20141208192521p:image

  • Subjectクラス

 RealSubjectクラスとProxyクラスが提供する共通のAPIを定義します。

  • RealSubjectクラス

 Subjectクラスのサブクラスで、Subjectクラスで宣言されたメソッドを実装します。このクラスが実際の処理を提供します。

 RealSubjectと同様、Subjectクラスのサブクラスで、Subjectクラスで宣言されたメソッドを実装します。ただし、具体的な処理は、内部に保持したRealSubjectクラスのインスタンスに転送します。

 また、このクラス内で、RealSubjectクラスに対するアクセス制御やRealSubjectクラスのインスタンスの生成タイミングなどを調整します。

Proxyパターンのメリット

 Proxyパターンのメリットとしては、以下のものが挙げられます。

 Proxyパターンを適用すると、クライアントは目的のオブジェクトに間接的にアクセスするようになります。間接的になることでProxyクラスにさまざまな機能を持たせることができ、目的のオブジェクトへのアクセス手段を変化させることができます。

Proxyパターンを適用する

 Proxyパターンの適用例を見てみましょう。

 ここでは、商品情報をデータベースから取得する簡単なアプリケーションを用意しました。Proxyパターンを適用し、同じAPIで実データを扱うクラスとダミーデータを扱うクラスを切り替えられるようにしています。

 まずは、商品クラスから説明します。

 Itemクラスは、純粋に商品の情報や注文の情報を内部に保持するだけの役割を担っており、コンストラクタに商品情報(商品IDと商品名)を受け取って、その値にアクセスするためのメソッドが用意されているだけです。特に難しいところはありませんね。

Itemクラス(Item.class.php
<?php
class Item
{
    private $id;
    private $name;
    public function __construct($id, $name)
    {
        $this->id = $id;
        $this->name = $name;
    }
    public function getId()
    {
        return $this->id;
    }
    public function getName()
    {
        return $this->name;
    }
}

 次はSubjectクラスに相当するItemDaoインターフェースです。サブクラスの共通APIとなるfindByIdメソッドを宣言しています。このメソッドは商品IDを引数として受け取り、該当するItemオブジェクトを返すためのものです。

ItemDaoインターフェース(ItemDao.class.php
<?php
interface ItemDao {
    public function findById($item_id);
}

 また、クライアントはこのfindByIdメソッドを通じて、次に挙げるDbItemDaoクラスとMockItemDaoクラスにアクセスします。

 DbItemDaoクラスはRealSubjectクラスに相当するクラスで、ItemDaoインターフェースを実装しています。このクラスは、実際のデータベースにアクセスして商品情報を取得します。

DbItemDaoクラス(DbItemDao.class.php
<?php
require_once 'ItemDao.class.php';
require_once 'Item.class.php';

class DbItemDao implements ItemDao
{
    public function findById($item_id)
    {
        $fp = fopen('item_data.txt', 'r');

        /**
         * ヘッダ行を抜く
         */
        $dummy = fgets($fp, 4096);

        $item = null;
        while ($buffer = fgets($fp, 4096)) {
            $id = trim(substr($buffer, 0, 10));
            $name = trim(substr($buffer, 10));

            if ($item_id === (int)$id) {
                $item = new Item($id, $name);
                break;
            }
        }

        fclose($fp);

        return $item;
    }
}

 このアプリケーションでは、ファイルに格納された商品情報を扱うようになっています。データは固定長データとして用意しました。データフォーマットの詳細は次の表を参照してください。なお、項目名はデータファイル内の先頭行にもあります。

商品情報(item_data.txt)
商品ID    商品名
1         限定Tシャツ
2         ぬいぐるみ
3         クッキーセット
商品情報のファイルフォーマット
項目開始位置終了位置備考
商品ID110
商品名11-

 一方のMockItemDaoクラスもItemDaoインターフェースを実装したRealSubjectクラスに相当するクラスです。しかし、findByIdメソッドで受け取った商品IDを使ってそのままItemオブジェクトを生成し、戻り値としています。つまり、どの様な商品IDを渡しても、商品名が「ダミー商品」となるItemオブジェクトが生成されます

MockItemDaoクラス(MockItemDao.class.php
<?php
require_once 'ItemDao.class.php';
require_once 'Item.class.php';

class MockItemDao implements ItemDao
{
    public function findById($item_id)
    {
        $item = new Item($item_id, 'ダミー商品');
        return $item;
    }
}

 続いて、ItemDaoProxyクラスを見てみましょう。ItemDaoProxyクラスもItemDaoインターフェースを実装したクラスですが、Proxyクラスに相当します。見てみると、実際の処理内容は、コンストラクタで受け取ったItemDao型のオブジェクトに委譲していることが分かると思います。

ItemDaoProxyクラス(ItemDaoProxy.class.php
<?php
class ItemDaoProxy
{
    private $dao;
    private $cache;
    public function __construct(ItemDao $dao)
    {
        $this->dao = $dao;
        $this->cache = array();
    }
    public function findById($item_id)
    {
        if (array_key_exists($item_id, $this->cache)) {
            echo '<font color="#dd0000">Proxyで保持しているキャッシュからデータを返します</font><br>';
            return $this->cache[$item_id];
        }

        $this->cache[$item_id] = $this->dao->findById($item_id);
        return $this->cache[$item_id];
    }
}

 ItemDaoProxyクラスには、簡易的ですが、findByIdメソッドでは商品情報のキャッシュ機能が実装されています。また、キャッシュからデータが取得された場合、メッセージを表示するようになっています。これは実際に動作を確認してみてくださいね。

 その他、Proxyクラスに相当するクラスには、データベースとの接続・初期化処理やデータの前処理、後処理などを記述することができます。

 最後にクライアント側コードの説明です。

 このコードでは、受け取ったPOSTパラメータ「dao」の値によって、DbItemDaoクラスとMockItemDaoクラスのいずれをインスタンス化するのかを決定しています。また、POSTパラメータproxy」の値によってItemDaoProxyクラスを使用するかどうかを切り替えます。使用する場合、生成したItemDaoオブジェクトをItemDaoProxyクラスのコンストラクタに渡しています。

クライアント側コード(proxy_client.php
<?php
if (isset($_POST['dao']) && isset($_POST['proxy'])) {
    $dao = $_POST['dao'];
    switch ($dao) {
        case 1:
            include_once 'MockItemDao.class.php';
            $dao = new MockItemDao();
            break;
        default:
            include_once 'DbItemDao.class.php';
            $dao = new DbItemDao();
            break;
    }

    $proxy = $_POST['proxy'];
    switch ($proxy) {
        case 1:
            include_once 'ItemDaoProxy.class.php';
            $dao = new ItemDaoProxy($dao);
            break;
    }

    for ($item_id = 1; $item_id <= 3; $item_id++) {
        $item = $dao->findById($item_id);
        echo 'ID=' . $item_id . 'の商品は「' . $item->getName() . '」です<br>';
    }

    /**
     * 再度データを取得
     */
    $item = $dao->findById(2);
    echo 'ID=' . $item_id . 'の商品は「' . $item->getName() . '」です<br>';
}
?>
<hr>
<form action="" method="post">
  <div>
    Daoの種類:
    <input type="radio" name="dao" value="0" checked>DbItemDao
    <input type="radio" name="dao" value="1">MockItemDao
  </div>
  <div>
    Proxyの利用:
    <input type="radio" name="proxy" value="0" checked>しない
    <input type="radio" name="proxy" value="1">する
  </div>
  <div>
    <input type="submit">
  </div>
</form>

 それぞれのインスタンスを生成した後に商品情報を取得していますが、ここは共通のコードとなっていることと、生成されるインスタンスによって動作を切り替えることができることを確認してください。

 また、Proxyパターンの適用したアプリケーションのクラス図は、次のようになります。

f:id:shimooka:20141208192517p:image

適用例の実行結果

 Proxyパターンを適用したサンプルの実行結果は、次のようになります。

サンプルの実行結果(DbItemDao+Proxy利用なし)

f:id:shimooka:20141208192520p:image

サンプルの実行結果(DbItemDao+Proxy利用あり)

f:id:shimooka:20141208192518p:image

サンプルの実行結果(MockItemDao+Proxy利用あり)

f:id:shimooka:20141208192519p:image

Proxyパターンのオブジェクト指向的要素

 Proxyパターンは「委譲」を多用したパターンです。

 ここまで見てきたように、Proxyパターンでは目的のオブジェクトを包み込むクラス(Proxyクラス)を用意し、クライアントはこのクラスで用意されたAPIを通じて目的のオブジェクトにアクセスします。また、Proxyクラスの内部では具体的な処理はおこなわず、包み込んだ目的のオブジェクトに最終的な処理を依頼しています。

 また、包み込む側のクラスは目的のオブジェクトと共通のAPIを持ちます。つまり、クライアントはこのAPIを使ってコードを書いている限り、Proxyオブジェクトでも目的のオブジェクトでも気にせず利用できる、ということを意味します。このことを「透過的である」といいます。

関連するパターン

  • Adapterパターン

 AdapterパターンはProxyパターンと同様、目的のオブジェクトを包み込むパターンですが、Adapterパターンは目的のオブジェクトが提供するAPIと異なるAPIを提供するためのパターンです。

 Decoratorパターンも共通インターフェースを持つクラスが内部に保持したインスタンスに具体的な処理を転送するパターンで、Proxyパターンとよく似ています。しかし、適用する目的が異なっています。

 Decoratorパターンは、あるオブジェクトに対して新しい機能を追加することを目的としていますが、Proxyパターンは、あるオブジェクトに対するアクセスを変更可能にするパターンです。

まとめ

 ここでは目的のオブジェクトの代理オブジェクトを用意するProxyパターンについて見てきました。

[][]PHPによるデザインパターン入門 - Prototype〜コピーして作る

このエントリは、Do You PHP?(www.doyouphp.jp)で公開していたコンテンツを移行/加筆/修正したものです。公開の経緯はこちらをどうぞ。目次はこちらです。サンプルコードを手直ししたものgithubに上げてありますのでそちらもどうぞ。

GoF本における分類

 生成+オブジェクト

はじめに

 ここではPrototypeパターンについて説明します。

 prototypeとはカタカナ言葉の「プロトタイプ」で、「試作品」「原型」といった意味があります。

 それでは、Prototypeパターンはそのプロトタイプを使って何をするパターンなのでしょうか?早速見ていきましょう。

たとえば

 ユーザーの操作を保存したオブジェクトを考えてみましょう。たとえば、アプリケーション上でのユーザの行動履歴を保持する監査クラスです。

 このオブジェクトには、ユーザがいつどの画面にアクセスし、どういう操作をおこなったか、という情報が保存されています。通常、ユーザーの操作は決まった手順に沿ったものにはなりません。つまり、対象のユーザによって、オブジェクトが保持する情報はバラバラになるということです。

 ここで、何らかの理由でこのオブジェクトと同じ内容のオブジェクトを生成する必要が出てきたとします。この場合、どのようにオブジェクトを生成すれば良いでしょうか?

 通常、オブジェクトはnew演算子を使って生成します。しかし、この監査オブジェクトのように、内部状態が決まっていないオブジェクト1から作り上げるのは難しそうです。仮にパラメータを渡して内部状態を設定するとしても、非常に面倒な作業になりそうです。

 この場合、対象のオブジェクトそのものをコピーして、もうひとつのインスタンスを生成した方が簡単そうです。

 ここで説明するPrototypeパターンは、原型となるインスタンスをコピーして新しいインスタンスを生成するためのパターンです。

Prototypeパターンとは?

 Prototypeパターンはオブジェクトの生成方法に注目したパターンで、オブジェクトをコピーして新しいオブジェクトを生成することを目的としています。

 GoF本では、Prototypeパターンの目的は次のように定義されています。

生成すべきオブジェクトの種類を原型となるインスタンスを使って明確にし、それをコピーすることで新たなオブジェクトの生成を行う。

 Prototypeパターンでは、親クラスでインスタンスをコピーするためのメソッドを定義します。このメソッド戻り値は、自分自身のクラス型となります。そして、そのサブクラスで自分自身のコピーを返すよう実装します。

 なお、Prototypeパターンを適用する場合、「浅いコピー」と「深いコピー」を意識して実装する必要がありますので、注意が必要です。

 PHPでは、バージョン5.0.0からcloneキーワードが追加されました。このcloneキーワードを使うことで、オブジェクトのコピーを作成できるようになりました。このcloneキーワードを使ったコピーは、「浅いコピー」(shallow copy)と呼ばれます。「浅いコピー」では、変数に格納された値がそのままコピーされます。具体的には、数値や文字列などは値として格納されているので、値そのものがコピーされます。これは期待する動作です。一方、オブジェクトの参照を格納した変数では、参照がコピーされることになります。この結果、コピー元とコピー先で、共通のオブジェクトを参照することになります。 つまり、「外側の器はコピーされたが、中身はコピーされていない」状態になります。これは期待する動作ではありません。つまり、オブジェクトをコピーして、別のオブジェクトとなったはずが、コピー元のオブジェクトを変更するとコピー先のオブジェクトまで変更されてしまう、といった問題が発生します。

 これを回避するには、「深いコピー」(deep copy)と呼ばれる方法でコピーする必要があります。「深いコピー」とは、内部の参照もコピーする方法です。内部の参照をコピーするような実装は、Prototypeパターンの親クラスで定義されたメソッド内、もしくは、__cloneメソッドで行います。__cloneメソッドは、cloneキーワードと共にPHP5.0.0で追加されたメソッドです。

Prototypeパターンの構造

 Prototypeパターンのクラス図と構成要素は、次のとおりです。

f:id:shimooka:20141208191732p:image

 コピーするためのメソッドを定義する親クラスです。

  • ConcretePrototypeクラス

 Prototypeクラスのサブクラスで、Productクラスで定義されたコピー用のメソッドを実装するクラスです。

 Prototype型のオブジェクトを利用して、新しいインスタンスを生成します。

Prototypeパターンのメリット

 Prototypeパターンのメリットとしては、以下のものが挙げられます。

 Prototypeパターンでは、利用側に対して実際にインスタンス化を行う具体的なクラスを隠蔽します。つまり、クライアントが意識しておく必要があるクラスの数を抑えることができます。

 意識しなければならないクラスの数が増えるということは、クラスどうしの関係が強くなり、再利用することが難しくなります。Prototypeパターンでは、クラスどうしの関係をゆるくする効果があると言えます。

 たとえば、ユーザーの操作を保存したオブジェクトがある場合を考えてみましょう。

 通常、ユーザーの操作は、決まった手順に沿ったものにはなりません。このため、同じ内容を持つインスタンスがもうひとつ必要になった場合、クラスをインスタンス化するのは非常に困難です。この場合、インスタンスをコピーして、もうひとつのインスタンスを生成した方が簡単です。

 Prototypeパターンを適用することで、このような複雑なインスタンスを生成できます。

 インスタンスを生成するパターンの代表例として、Factory Methodパターンがあります。Factory Methodパターンでは、生成する側(Creator)と生成される側(Product)それぞれのクラス階層で継承関係を持ちます。また、インスタンスを生成する専用のメソッドfactory method)を用意します。

 Prototypeパターンでは、生成する側のクラス階層は必要ありません。インスタンスを生成する専用メソッドを呼び出す代わりに、生成される側のコピー用メソッドを呼び出すだけです。

Prototypeパターンの適用

 Prototypeパターンの適用例を見ていきます。

 ここでは、商品クラスをインスタンス化しコピーした結果を比較する簡単なアプリケーションを用意しました。このサンプルは、深いコピーと浅いコピーの例を確認できるようになっていますので、そこに注目しながらコードを見ていきましょう

 まずは、コピーするためのメソッドを定義しているItemPrototypeクラスです。Prototypeクラスに相当します。

ItemPrototypeクラス(ItemPrototype.class.php
<?php
/**
 * Prototypeクラスに相当する
 */
abstract class ItemPrototype
{
    private $item_code;
    private $item_name;
    private $price;
    private $detail;

    public function __construct($code, $name, $price)
    {
        $this->item_code = $code;
        $this->item_name = $name;
        $this->price = $price;
    }

    public function getCode()
    {
        return $this->item_code;
    }

    public function getName()
    {
        return $this->item_name;
    }

    public function getPrice()
    {
        return $this->price;
    }

    public function setDetail(stdClass $detail)
    {
        $this->detail = $detail;
    }

    public function getDetail()
    {
        return $this->detail;
    }

    public function dumpData()
    {
        echo '<dl>';
        echo '<dt>' . $this->getName() . '</dt>';
        echo '<dd>商品番号:' . $this->getCode() . '</dd>';
        echo '<dd>\\' . number_format($this->getPrice()) . '-</dd>';
        echo '<dd>' . $this->detail->comment . '</dd>';
        echo '</dl>';
    }

    /**
     * cloneキーワードを使って新しいインスタンスを作成する
     */
    public function newInstance()
    {
        $new_instance = clone $this;
        return $new_instance;
    }

    /**
     * protectedメソッドにする事で、外部から直接cloneされない
     * ようにしている
     */
    protected abstract function __clone();
}

 このクラスで注目するのは、newInstanceメソッドと__cloneメソッドです。newInstanceメソッドでは、cloneキーワードを使って自分自身のコピーを作っています。__cloneメソッドは抽象メソッドとなっており、サブクラスで異なる実装をおこなっています。

 また、内部にstdClass型の詳細情報オブジェクトを保持するようになっていますが、これについては、次のDeepCopyItemクラスとShallowCopyItemクラスで説明します。

 続いて、ConcretePrototypeクラスに相当するDeepCopyItemクラスとShallowCopyItemクラスです。名前の通り、DeepCopyItemクラスは深いコピー、ShallowCopyItemクラスは浅いコピーをそれぞれおこなう商品クラスです。

DeepCopyItemクラス(DeepCopyItem.class.php
<?php
require_once 'ItemPrototype.class.php';

/**
 * ConcretePrototypeクラスに相当する
 */
class DeepCopyItem extends ItemPrototype
{
    /**
     * 深いコピーを行うための実装
     * 内部で保持しているオブジェクトもコピー
     */
    protected function __clone()
    {
        $this->setDetail(clone $this->getDetail());
    }

}
ShallowCopyItemクラス(ShallowCopyItem.class.php
<?php
require_once 'ItemPrototype.class.php';

/**
 * ConcretePrototypeクラスに相当する
 */
class ShallowCopyItem extends ItemPrototype
{

    /**
     * 浅いコピーを行うので、空の実装を行う
     */
    protected function __clone()
    {
    }
}

 先ほど出てきた__cloneメソッドを実装しているクラスですが、両クラスで実装内容が異なります。DeepCopyItemクラスでは、内部に保持したstdClass型の詳細情報オブジェクトをコピーしています。つまり、DeepCopyItemクラスをコピーしたときに、内部の詳細情報オブジェクトも併せてコピーされるということです。一方のShallowCopyItemクラスでは、__cloneメソッドは空の実装がなされています。これは、コピーしたときに詳細情報オブジェクトはコピーされないということですが、この違いがどう現れるのでしょうか。コードを一通り説明したあとに動作結果を見てみることにして、次に行きたいと思います。

 次は、商品オブジェクトを管理するItemManagerクラスです。Clientクラスに相当します。

 このクラスはコピーするオブジェクトを管理しつつ、新しいインスタンスを要求されたときにオブジェクトをコピーする役割を担っています。createメソッドを確認してください。

ItemManagerクラス(ItemManager.class.php
<?php
require_once 'ItemPrototype.class.php';

/**
 * Clientクラスに相当する
 * このクラスからはConcretePrototypeクラスは見えていない
 */
class ItemManager
{
    private $items;

    public function __construct()
    {
        $this->items = array();
    }

    public function registItem(ItemPrototype $item)
    {
        $this->items[$item->getCode()] = $item;
    }

    /**
     * Prototypeクラスのメソッドを使って、新しいインスタンスを作成
     */
    public function create($item_code)
    {
        if (!array_key_exists($item_code, $this->items)) {
            throw new Exception('item_code [' . $item_code . '] not exists !');
        }
        $cloned_item = $this->items[$item_code]->newInstance();

        return $cloned_item;
    }
}

 ここまで見てきたクラス群を利用するクライアント側のコードも見ておきましょう。

クライアント側コード(prototype_client.class.php
<?php
require_once 'ItemManager.class.php';
require_once 'DeepCopyItem.class.php';
require_once 'ShallowCopyItem.class.php';

function testCopy(ItemManager $manager, $item_code) {
    /**
     * 商品のインスタンスを2つ作成
     */
    $item1 = $manager->create($item_code);
    $item2 = $manager->create($item_code);

    /**
     * 1つだけコメントを削除
     */
    $item2->getDetail()->comment = 'コメントを書き換えました';

    /**
     * 商品情報を表示
     * 深いコピーをした場合、$item2への変更は$item1に影響しない
     */
    echo '■オリジナル';
    $item1->dumpData();
    echo '■コピー';
    $item2->dumpData();
    echo '<hr>';
}

$manager = new ItemManager();

/**
 * 商品データを登録
 */
$item = new DeepCopyItem('ABC0001', '限定Tシャツ', 3800);
$detail = new stdClass();
$detail->comment = '商品Aのコメントです';
$item->setDetail($detail);
$manager->registItem($item);

$item = new ShallowCopyItem('ABC0002', 'ぬいぐるみ', 1500);
$detail = new stdClass();
$detail->comment = '商品Bのコメントです';
$item->setDetail($detail);
$manager->registItem($item);

testCopy($manager, 'ABC0001');
testCopy($manager, 'ABC0002');

 ここでは、それぞれの商品クラスをインスタンス化してItemManagerオブジェクトに登録し、そこから商品オブジェクトのコピーを2つ作成しています。そして、その片方に変更を加えたあと、オブジェクトの内容を表示しています。

 最後に、Prototypeパターンを適用したサンプルのクラス図を確認しておきましょう。

f:id:shimooka:20141208191730p:image

適用例の実行結果

 Prototypeパターンを適用したサンプルの実行結果は、次のようになります。

f:id:shimooka:20141208191731p:image

 深いコピーをおこなうDeepCopyItemクラスの場合、どちらかのオブジェクトを変更しても他方のオブジェクトに影響を与えていませんが、浅いコピーをおこなうShallowCopyItemクラスの場合、一方のオブジェクトが他方の影響を与えてしまっています。違いがお分かりでしょうか?

 このように、Prototypeパターンを適用する場合は、深いコピーなのか浅いコピーなのかを必ず意識する必要があります

Prototypeパターンのオブジェクト指向的要素

 Prototypeパターンは「継承」と「ポリモーフィズム」を利用しているパターンです。

 PrototypeクラスとConcretePrototypeクラスの間には継承関係があります。親クラスであるPrototypeクラスでは、インスタンスを生成するためのメソッドを用意します。Prototypeは抽象クラス、もしくはインターフェースで実装されます。親クラスを継承、もしくは実装したクラスがConcretePrototypeクラスです。ConcretePrototypeクラスでは、インスタンスを生成する処理を具体的に実装します。

 Clientクラスでは、Prototypeクラスで提供されているAPIのみを使って、プログラミングを行います。具体的なConcretePrototypeクラスに関することを一切書かないわけです。こうすることで、ConcretePrototypeクラスに依存しないコードになり、Clientクラス側とPrototypeクラス側は独立して修正を行うことができます。

 また、ClientクラスとConcretePrototypeクラスに依存関係がないお陰で、Clientクラスを修正することなく、ConcretePrototypeクラスを差し替えたり、新しく追加したりできます。

関連するパターン

 Abstract FactoryパターンとPrototypeパターンが併用される場合があります。つまり、Abstract Factoryから返すインスタンスを、インスタンス化するのではなく、コピーすることで生成するのです。

 CompositeパターンやDecoratorパターンを使って、複雑な構造を持つオブジェクトを動的に生成する場面があります。こういった場合、Prototypeパターンが有効に使える場合があります。

まとめ

 ここではインスタンスをコピーして新しいインスタンスを生成するPrototypeパターンについて見てきました。

[][]PHPによるデザインパターン入門 - Observer〜状態変化を通知する

このエントリは、Do You PHP?(www.doyouphp.jp)で公開していたコンテンツを移行/加筆/修正したものです。公開の経緯はこちらをどうぞ。目次はこちらです。サンプルコードを手直ししたものgithubに上げてありますのでそちらもどうぞ。

GoF本における分類

 振る舞い+オブジェクト

はじめに

 ここではObserverパターンについて説明します。

 observerという単語は「observe」する人、つまり「観察者」「観測者」の意味です。ということは、「観察」する対象はやはりオブジェクトということになりそうですね。

 実際そのとおりで、観察対象のオブジェクトが変化したときに通知してもらい、その変化を他のオブジェクトにも伝えるためのパターンです。

 では、Observerパターンを見ていきます。

たとえば

 たとえば、複数のオブジェクトが連携して動作する場面を考えてみましょう。

 こういった場面では、あるオブジェクトの状態に変化があった場合、矛盾を起こさないよう他のオブジェクトもその変化にあわせて振る舞いを変える必要が出てきます。

 では、この連係動作をどうやって管理すれば良いでしょうか?

 最も簡単な方法は、「このオブジェクトが変化したらこうする」といったコードを記述することでしょう。しかし、こういったある特定のクラスに依存したコードを記述するとクラスどうしの関係が緊密になってしまい、クラスの再利用性を下げることになります。

 また、連携し合うオブジェクトの数が少なければオブジェクトどうしの関連はそれほど複雑にはなりませんので、あまり大きな問題になることは少ないでしょう。しかし、連携するオブジェクトの数が多い場合はどうでしょうか?最後には誰も管理できないぐらい複雑になってしまいますね?

 ここで、Observerパターンの登場です。Observerパターンはクラスどうしの結合をゆるく保ったまま、協調動作を実現するパターンです。

Observerパターンとは?

 Observerパターンはオブジェクトの振る舞いに注目したパターンで、オブジェクトの変化を他のオブジェクトに通知することを目的としています。

 GoF本では、Observerパターンの目的は次のように定義されています。

あるオブジェクトが状態を変えたときに、それに依存するすべてのオブジェクトに自動的にそのことが知らされ、また、それらが更新されるように、オブジェクト間に一対多の依存関係を定義する。

 Observerパターンは、観測対象のオブジェクトに変化があったとき、それを観測しているすべてのオブジェクトに通知を行うためのパターンです。つまり、状態変化に応じた処理を行う場合に有効なパターンと言えます。

 Observerパターンでは、観測対象のクラスとそれを観測するクラスをそれぞれ用意します。観測対象クラスは、内部に観測クラスのインスタンスを複数保持できる構造になっています。一方の観測クラスは、通知を受け取るための共通のAPIを持っています。そして、観測対象クラスの状態が変化したとき、保持した観測インスタンスの通知用メソッドを呼び出し、「状態が変化した」との通知をおこないます。

 このとおり、観測対象クラスと観測クラスの間に強い結びつきはありませんが、協調動作するための仕組みはちゃんと用意されています。お気づきになりましたか?

Observerパターンの構成要素

 Observerパターンのクラス図と構成要素は、次のようになります。

f:id:shimooka:20141208191529p:image

  • Subjectクラス

 観測対象のクラスです。内部には観測クラスであるObserver型のオブジェクトを保持しており、Observer型のオブジェクトを登録、削除するAPI、またObserver型のオブジェクトに通知をおこなうAPIが提供されています。通知をおこなう場合、SubjectクラスからはObserverクラスで定義されたAPIを通じて、ConcreteObserverクラスで実装されている具体的な処理を呼び出します。

  • Observerクラス

 観測クラスです。通知を受け取るためのAPIを定義します。具体的な処理内容は、サブクラスのConcreteObserverクラスで実装します。Listener、Handlerと呼ばれる場合もあります。

  • ConcreteSubjectクラス

 Subjectクラスのサブクラスで、Observerクラスに影響する状態を保持しています。また、その状態を取得するためのAPIを提供しています。この保持した状態が変化したとき、Observer型のオブジェクトに通知を送ります。

  • ConcreteObserverクラス

 Observerクラスのサブクラスで、Observerクラスで定義されたAPIを実装したクラスです。このクラスに通知を受け取った場合の具体的な処理内容を記述します。

 なお、ConcreteObserverクラスどうしは、通知を受ける順番が変わっても正しく動作するよう設計される必要があります。

Observerパターンのメリット

 Observerパターンのメリットとしては、以下のものが挙げられます。

  • ConcreteSubjectクラスとConcreteObserverクラスを独立して拡張できる

 Observerパターンは、オブジェクトどうしをゆるく結合したまま協調動作をさせることを可能にします。つまり、ConcreteSubjectクラスとConcreteObserverクラスの間に直接の関係はありません。両クラスの関係を作っているのは、それぞれの親クラスであるSubjectクラスとObserverクラスです。このため、それぞれ独立して修正・拡張することができます。また、新しいConcreteObserverクラスを追加する場合もSubjectクラスやConcreteSubjectクラスを修正する必要はありません。

Observerパターンの適用

 ここではECサイトでよく見かけるショッピングカートを取り上げ、ショッピングカートの状態が変化するたびに様々なオブジェクトと連係動作をおこなう様子を見てみましょう。

 このサンプルでは、観察対象としてショッピングカート、観察者として次の2つを用意しました。

  • ショッピングカートにある特定の商品が追加された場合、プレゼント商品を追加する観察者
  • ショッピングカートの内容を出力する観察者

 これら観察者の具体的なコードは後ほど出てきますので、まずは観察対象であるショッピングカートを表すCartクラスから見てみましょう。

Cartクラス(Cart.class.php
<?php
/**
 * Subjectクラス+ConcreteSubjectクラスに相当する
 */
class Cart
{

    private $items;
    private $listeners;

    public function __construct()
    {
        $this->items = array();
        $this->listeners = array();
    }

    public function addItem($item_cd)
    {
        $this->items[$item_cd] = (isset($this->items[$item_cd]) ? ++$this->items[$item_cd] : 1);
        $this->notify();
    }

    public function removeItem($item_cd)
    {
        $this->items[$item_cd] = (isset($this->items[$item_cd]) ? --$this->items[$item_cd] : 0);
        if ($this->items[$item_cd] <= 0) {
            unset($this->items[$item_cd]);
        }
        $this->notify();
    }

    public function getItems()
    {
        return $this->items;
    }

    public function hasItem($item_cd)
    {
        return array_key_exists($item_cd, $this->items);
    }

    /**
     * Observerを登録するメソッド
     */
    public function addListener(CartListener $listener)
    {
        $this->listeners[get_class($listener)] = $listener;
    }

    /**
     * Observerを削除するメソッド
     */
    public function removeListener(CartListner $listener)
    {
        unset($this->listeners[get_class($listener)]);
    }

    /**
     * Observerへ通知するメソッド
     */
    public function notify()
    {
        foreach ($this->listeners as $listener) {
            $listener->update($this);
        }
    }
}

 Cartクラスには、まずショッピングカートとして必要となるメソッドが用意されています。商品を追加するメソッド(addItem)、削除するメソッド(removeItem)、全ての商品を取り出すメソッド(getItems)、商品がすでにショッピングカートに入れられているかどうかを返すメソッド(hasItem)です。

 getItemsメソッドとhasItemメソッドについては、観察者のところでも出てきますので、もうちょっと待っていてください。

 このほかにaddListenerメソッドとremoveListenerメソッド、notifyメソッドがありますね。

 addListenerメソッドとremoveListenerメソッドの2つは、ショッピングカートの状態変化を観察する観察者を登録、削除するメソッドです。

 notifyメソッドは登録された観察者に「状態が変わりましたよ」と通知するためのメソッドです。通知をおこなう際、変化した具体的な内容を観察者に渡す必要がありますが、今回はCartオブジェクト自身を引数として渡しています。

 また、登録された全ての観察者に対して通知をおこなっている事が分かると思います。

 この通知を受け取った観察者は、その変化に対応する動作をおこなうことになります。

 次に、観察者側のクラスたちを見ていきます。

 まずは、観察者に共通するAPIを定義するCartListenerクラスです。ここではinterfaceとして定義しており、唯一のメソッドであるupdateメソッドを定義しています。引数としては、Cartオブジェクトを受け取ります。この渡されたCartオブジェクトから、変更内容を直接取り出すことができます。この具体的に取り出す様子は、次のPresentListenerクラスとLoggingListenerクラスで見てみましょう。

CartListenerクラス(CartListener.class.php
<?php
/**
 * Observerクラスに相当する
 */
interface CartListener {
    public function update(Cart $cart);
}

 PresentListenerクラスとLoggingListenerクラスはCartListenerクラスを実装したクラスで、具体的な観察者となります。

 PresentListenerクラスは、特定の商品が追加された場合にプレゼント商品を追加します。具体的には、クッキーセットがショッピングカートに含まれる場合、プレゼント商品として「プレゼント」をショッピングカートに追加します。

PresentListenerクラス(PresentListener.class.php
<?php
require_once 'CartListener.class.php';

/**
 * ConcreteObserverクラスに相当する
 */
class PresentListener implements CartListener
{

    private static $PRESENT_TARGET_ITEM = '30:クッキーセット';
    private static $PRESENT_ITEM = '99:プレゼント';

    public function __construct()
    {
    }

    public function update(Cart $cart)
    {
        if ($cart->hasItem(self::$PRESENT_TARGET_ITEM) &&
            !$cart->hasItem(self::$PRESENT_ITEM)) {
            $cart->addItem(self::$PRESENT_ITEM);
        }
        if (!$cart->hasItem(self::$PRESENT_TARGET_ITEM) &&
            $cart->hasItem(self::$PRESENT_ITEM)) {
            $cart->removeItem(self::$PRESENT_ITEM);
        }
    }
}

 LoggingListenerクラスは、ショッピングカートの状態が変化するたびにvar_dump関数を使ってショッピングカートに含まれている商品と個数を出力します。

LoggingListenerクラス(LoggingListener.class.php
<?php
require_once 'CartListener.class.php';

/**
 * ConcreteObserverクラスに相当する
 */
class LoggingListener implements CartListener
{

    public function __construct()
    {
    }

    public function update(Cart $cart)
    {
        echo '<pre>';
        var_dump($cart->getItems());
        echo '</pre>';
    }
}

 PresentListenerクラスとLoggingListenerクラス共に、updateメソッドに渡されたCartオブジェクトからショッピングカートの状態を取得し、処理をおこなっていることが分かったと思います。PresentListenerクラスではhasItemメソッド、LoggingListenerクラスではgetItemsメソッドを使っていますね。こうやって、観察者はうまい具合に観測対象の情報を取得することができています。

 なお、観察対象のクラスは観察者に「どこまで情報を公開するか」を検討する必要があります。これはObserverパターンを適用する上で重要なポイントになってきます。

 最後に利用側のコードです。

 ショッピングカートの生成をcreateCart関数にまとめてあります。

 また、Observerパターンの特徴の1つとして観察者を動的に追加、削除することが挙げられますが、createCart関数の中でCartオブジェクトを生成したあとに観察者であるPresentListenerクラスとLoggingListenerクラスの各インスタンスを追加していることが分かると思います。

 たとえば、「開発中はロギングするが、リリースするときはロギングしない」といった「ある条件の場合だけ別の観察者を追加・削除する」といったことも可能になります。

クライアント側コード(observer_client.php
<?php
require_once 'Cart.class.php';
require_once 'PresentListener.class.php';
require_once 'LoggingListener.class.php';

function createCart() {
    $cart = new Cart();
    $cart->addListener(new PresentListener());
    $cart->addListener(new LoggingListener());

    return $cart;
}

    session_start();

    $cart = isset($_SESSION['cart']) ? $_SESSION['cart'] : null;
    if (is_null($cart)) {
        $cart = createCart();
    }

    $item = (isset($_POST['item']) ? $_POST['item'] : '');
    $mode = (isset($_POST['mode']) ? $_POST['mode'] : '');
    switch ($mode) {
        case 'add':
            echo '<p style="color: #aa0000">追加しました</p>';
            $cart->addItem($item);
            break;
        case 'remove':
            echo '<p style="color: #008800">削除しました</p>';
            $cart->removeItem($item);
            break;
        case 'clear':
            echo '<p style="color: #008800">クリアしました</p>';
            $cart = createCart();
            break;
    }

    $_SESSION['cart'] = $cart;

    echo '<h1>商品一覧</h1>';
    echo '<ul>';
    foreach ($cart->getItems() as $item_name => $quantity) {
        echo '<li>' . $item_name . ' ' . $quantity . '個</li>';
    }
?>
<form action="" method="post">
<select name="item">
<option value="10:Tシャツ">Tシャツ</option>
<option value="20:ぬいぐるみ">ぬいぐるみ</option>
<option value="30:クッキーセット">クッキーセット</option>
</select>
<input type="submit" name="mode" value="add">
<input type="submit" name="mode" value="remove">
<input type="submit" name="mode" value="clear">
</form>

 まとめとして、このサンプルコードのクラス図を示しておきます。

f:id:shimooka:20141208191527p:image

適用例の実行結果

 Observerパターンを適用したサンプルの実行結果です。たとえば、Tシャツとクッキーセットを「add」ボタンを使って追加すると、次のようになります。

f:id:shimooka:20141208191528p:image

Observerパターンのオブジェクト指向的要素

 Observerパターンは「ポリモーフィズム」を活用しているパターンです。

 観測対象クラスの親クラスであるSubjectクラスでは、Observer型のインスタンスを内部に保持しています。このインスタンスは、Observerクラスを継承したConcreteObserverクラスのインスタンスです。Observerクラスは、内部の状態が変更したとき、保持したObserverインスタンスに「状態が変化しましたよ」という通知を送ります。一方のObserverクラスは、観測対象クラスから送られた通知を元におこなう処理だけを定義・実装しています。

 SubjectクラスはObserverクラスに通知するだけObserverクラスは受けた通知を処理するだけ、という関係しかありません。「いつ通知を出すか」「通知を元にどういった処理をするか」は、それぞれのサブクラスに任されています。つまり、ConcreteSubjectクラスとConcreteObserverクラスは直接お互いを知りませんが、親クラスどうしで作られた関係を利用してお互いにやりとりを行います。

 この結果、ConcreteObserverクラスを簡単に差し替えたり、追加したりできるのです。

関連するパターン

 Observerパターンと同様、状態変化を通知するパターンです。Observerパターンでは、Observerクラス自体が通知処理をおこないますが、Mediatorパターンは通知の仲介をおこなうだけです。 また、Observerパターンでは、単一オブジェクトの状態変化を複数のオブジェクトへ通知しますが、Mediatorパターンでは複数のオブジェクトの状態変化があるオブジェクトに集約されて、そこから他のオブジェクトに通知されます。

まとめ

 ここではオブジェクトどうしの協調動作を実現するObserverパターンを見てきました。

[][]PHPによるデザインパターン入門 - Memento〜スナップショットを取る

このエントリは、Do You PHP?(www.doyouphp.jp)で公開していたコンテンツを移行/加筆/修正したものです。公開の経緯はこちらをどうぞ。目次はこちらです。サンプルコードを手直ししたものgithubに上げてありますのでそちらもどうぞ。

GoF本における分類

 振る舞い+オブジェクト

はじめに

 ここではMementoパターンについて説明します。

 「memento」とは聞き慣れない単語と思いますが、「記念品」「形見」「記憶」「思い出」といった意味があります。Mementoパターンは「Snapshotパターン」とも呼ばれますが、これなら分かりやすいでしょうか。Mementoパターンとは、ある時点のオブジェクトの記憶を保存し、あとで思い出せるようにするパターンです。

 では、早速Mementoパターンを見ていきましょう。

たとえば

 表計算ソフトワードプロセッサテキストエディタなどのデスクトップアプリケーションを考えてみましょう。

 ほとんどのアプリケーションでアンドゥ(Undo:やり直し)がサポートされています。アンドゥとは「ある状態を保存しておいて、その状態に戻せるようにしておく」機能です。また、リドゥ(Redo:やり直しのやり直し)をサポートしたアプリケーションも多いですね。リドゥも広い意味でアンドゥと同じと考えて良いと思います。なぜなら、リドゥは「アンドゥする前の状態を保存しておいて、その状態に戻せるようにしておく」機能だからです。

 それでは、アンドゥやリドゥという機能はどういう風に実現しているのでしょうか?

 オブジェクト思考的に考えてみると、「ある状態」というのはオブジェクトとして表せそうですね。そうすると、ある状態のオブジェクトをそのままどこかに保存しておき、必要なときにそのオブジェクトに置き変えてしまえばアンドゥが実現できそうです。

 ここで言っている「状態」とはオブジェクト内部の状態、つまりオブジェクト内部に保持している値のことです。ですので、アンドゥを実現するために保存しておく必要があるのは、内部に保持している値そのもの、ということで良さそうです。ただし、アンドゥして期待した状態に復元させるには、保存した値を厳重に管理しなければならないでしょう。

 いかがでしょうか?何となくイメージできましたか?

 Mementoパターンは、これまで説明してきた内容そのままに、アンドゥを実現するパターンなのです。

Mementoパターンとは?

 Mementoパターンはオブジェクトの振る舞いに注目したパターンで、オブジェクトのスナップショットを採ることを目的としています。

 GoF本では、Mementoパターンの目的は次のように定義されています。

カプセル化を破壊せずに、オブジェクトの内部状態を捉えて外面化しておき、オブジェクトを後にこの状態に戻すことができるようにする。

 Mementoパターンでは、ある時点のオブジェクトの状態を別のオブジェクトとして保存しておき、状態が変化した場合でも、その時の状態に戻すことを可能にするパターンです

 Mementoパターンは、あるオブジェクト記憶を保存するための専用のクラスと、その保存を管理するクラスから構成されます。

 あるオブジェクトが記憶を保存する場合は、保存用のクラスをインスタンス化し、自分の記憶を詰め込みます。保存する記憶は、必要となる一部だけで構いませんが、他のクラスから記憶を書き換えられないよう注意する必要があります。そして、管理用のクラスに渡し、後で取り出せるよう管理してもらいます。逆に、記憶を戻したいときは、管理クラスに渡した記憶オブジェクトを取得し、自分自身に渡して記憶を復元します。管理クラスは、その記憶オブジェクトを操作したり、中を覗いたりしません。あくまで渡された記憶オブジェクトを管理するだけの役目を持ちます。

 Mementoパターンは、状態を生成するクラスとその履歴の管理をするクラスを分離するパターンとも言えますね。

Mementoパターンの構造

 Mementoパターンのクラス図と構成要素は、次のとおりです。

f:id:shimooka:20141208191216p:image

 Originatorオブジェクトの記憶を保持する記憶用クラスです。Originatorオブジェクトの内部状態を保持します。厳密には、Originator以外のオブジェクトによってアクセスさせないようにする必要があります。理想的には、Mementoオブジェクトはそのオブジェクトを生成したOriginatorからのみアクセスできるようにすべきです。つまり、Caretakerオブジェクトなどの他のオブジェクトによって、内部状態を変化させないようにしなければなりません。保存した記憶の内容が変わってしまうからです。しかし残念ながら、PHP5には「特定のクラスだけにアクセスを許可する」といった制御機構はありません。これを回避するには、本来のMementoパターンの構造を若干変更する必要があります。

  • Originatorクラス

 オブジェクトの内部状態を保存される側のクラスです。内部状態をMementoオブジェクトに詰め込み、それを返すメソッドを持ちます。

  • Caretakerクラス

 Mementoオブジェクトを管理するクラスです。

Mementoパターンの適用

 Mementoパターンの適用例として、1行コメントを入力する小さなメモ帳を作ってみましょう。

 このメモ帳はコメントを登録できるだけでなく、ある時点の状態を保存しておくことができます。また、その状態に戻すこともできる、というものです。

 まずは、DataSnapshotクラスから見ていきましょう。DataSnapshotクラスはMementoクラスに相当するクラスで、「記憶」であるひとつひとつのコメントを保存します。コンストラクタとコメントを取り出すgetCommentメソッドがprotectedとして定義されていますが、これについては次のDataクラスで説明しています。

 その他は、特に難しいところはないと思います。

DataSnapshotクラス(DataSnapshot.class.php
<?php
/**
 * Mementoに相当する
 */
class DataSnapshot
{

    private $comment;

    protected function __construct($comment)
    {
        $this->comment = $comment;
    }

    protected function getComment()
    {
        return $this->comment;
    }
}

 次に、メモ帳のデータを表すDataクラスです。ここではDataSnapshotクラスのサブクラスとして定義しています。また、finalクラスとなっていることにも気づいたでしょうか?

 これは構成要素のところでも説明しましたが、本来Mementoオブジェクトはそのオブジェクトを生成したOriginatorからのみアクセスできるようにする必要があります。さもないと、「記憶」であるDataSnapshotオブジェクトの内容が他のクラスから変更されてしまう可能性があるためです。

 これを回避するためには、PHP5では継承とprotectedメソッドを組み合わせる必要があります。これにより、MementoオブジェクトであるDataSnapshotオブジェクトメソッドには、Dataクラス以外からアクセスすることはできなくなり、記憶の保全が保証されます。その代わり、クラスが大きくなってしまいますので注意が必要です。

 その他、スナップショットを生成するtakeSnapshotメソッドやスナップショットから状態を復元するrestoreSnapshotメソッドも用意されています。

Dataクラス(Data.class.php
<?php
require_once 'DataSnapshot.class.php';

/**
 * Originatorに相当する
 */
final class Data extends DataSnapshot
{
    private $comment;

    /**
     * コンストラクタ
     */
    public function __construct()
    {
        $this->comment = array();
    }

    /**
     * Mementoを生成する
     */
    public function takeSnapshot()
    {
        return new DataSnapshot($this->comment);
    }

    /**
     * Mementoから復元する
     */
    public function restoreSnapshot(DataSnapshot $snapshot)
    {
        $this->comment = $snapshot->getComment();
    }

    public function addComment($comment)
    {
        $this->comment[] = $comment;
    }

    public function getComment()
    {
        return $this->comment;
    }
}

 次はCaretakerクラスに相当するDataCaretakerクラスです。このサンプルではスナップショットの保存先としてPHPセッション機能を利用していますが、セッションの開始やセッションとのデータのやりとりをおこなっています。

DataCaretakerクラス(DataCaretaker.class.php
<?php
/**
 * Caretakerに相当する
 */
class DataCaretaker
{

    public function __construct()
    {
        if (!isset($_SESSION)) {
            session_start();
        }
    }

    public function setSnapshot($snapshot)
    {
        $this->snapshot = $snapshot;
        $_SESSION['snapshot'] = $this->snapshot;
    }

    public function getSnapshot()
    {
        return (isset($_SESSION['snapshot']) ? $_SESSION['snapshot'] : null);
    }
}

 最後にクライアント側のコードです。コメントの一覧と入力用HTMLフォームの表示を表示します。

 コメントの状態のスナップショットを取る場合は、Dataオブジェクトからスナップショットを作成し、DataCaretakerオブジェクトに渡して保存してもらっています。また、状態を復元する際は、逆にDataCaretakerオブジェクトから「記憶」を取り出し、Dataオブジェクトに渡しています。

クライアント側コード(memento_client.php
<?php
require_once 'Data.class.php';
require_once 'DataCaretaker.class.php';

session_start();

$caretaker = new DataCaretaker();
$data = isset($_SESSION['data']) ? $_SESSION['data'] : new Data();

$mode = (isset($_POST['mode'])? $_POST['mode'] : '');

switch ($mode) {
    case 'add':
        /**
         * コメントをDataオブジェクトに登録する
         * 現時点のコメントはセッションに保存している事に注意
         */
        $data->addComment((isset($_POST['comment']) ? $_POST['comment'] : ''));
        break;
    case 'save':
        /**
         * データのスナップショットを取り、DataCaretakerに依頼して
         * 保存する
         */
        $caretaker->setSnapshot($data->takeSnapshot());
        echo '<font style="color: #dd0000;">データを保存しました。</font><br>';
        break;
    case 'restore':
        /**
         * DataCaretakerに依頼して保存したスナップショットを取得し、
         * データを復元する
         */
        $data->restoreSnapshot($caretaker->getSnapshot());
        echo '<font style="color: #00aa00;">データを復元しました。</font><br>';
        break;
    case 'clear':
        $data = new Data();
}

/**
 * 登録したコメントを表示する
 */
echo '今までのコメント';
if (!is_null($data)) {
    echo '<ol>';
    foreach ($data->getComment() as $comment) {
        echo '<li>' 
        . htmlspecialchars($comment, ENT_QUOTES, mb_internal_encoding())
        . '</li>';
    }
    echo '</ol>';
}

/**
 * 次のアクセスで使うデータをセッションに保存
 */
$_SESSION['data'] = $data;
?>
<form action="" method="post">
コメント:<input type="text" name="comment"><br>
<input type="submit" name="mode" value="add">
<input type="submit" name="mode" value="save">
<input type="submit" name="mode" value="restore">
<input type="submit" name="mode" value="clear">
</form>

 このサンプルコードのクラス図は、次のようになります。

f:id:shimooka:20141208191212p:image

Mementoパターンのメリット

 Mementoパターンのメリットとしては、以下のものが挙げられます。

 Mementoパターンを適用する目的でもあります。サンプルでは、1つ前の状態にしか戻せませんが、複数の記憶を管理することもできます。

  • Originatorクラスを単純なものにする

 Caretakerクラスに記憶を管理させることで、Originatorクラスを単純にすることができます。

適用例の実行結果

 Mementoパターンを適用したサンプルの実行結果です。まずは、コメントを3つ追加し保存した状態です。

f:id:shimooka:20141208191215p:image

続けて、もう1つコメントを追加すると次のようになります。

f:id:shimooka:20141208191213p:image

ここで、「restore」ボタンをクリックし、保存した状態に戻すと次のようになります。

f:id:shimooka:20141208191214p:image

Mementoパターンのオブジェクト指向的要素

 「オブジェクト指向的な要素」という視点から見ると、Mementoパターンはちょっと特殊な形をしています。ここでは、「クラスが負う責任」と「APIの公開」について見ていきましょう。

 まずは、クラスが負う責任についてです。Mementoパターンでは、記憶オブジェクトを作るOriginatorクラスと、それを管理するCaretakerクラスが分かれています。一見、同じクラスで実装してしまっても問題がなさそうに見えます。

 では、それぞれのクラスが負っている責任を見てみましょう。

 Originatorクラスの責任は、Mementoクラスのインスタンスを生成することと、Mementoオブジェクトから自分の状態を元に戻すことです。

 一方、Caretakerクラスの責任は、Mementoオブジェクトを管理することです。また、いつ記憶を取るか、いつ記憶を戻すかを決めます。

OriginatorクラスとCaretakerクラスの責任
クラス責任
OriginatorMementoクラスのインスタンスを生成する。Mementoオブジェクトから自分の状態を元に戻す
CaretakerMementoオブジェクトを管理する。記憶を取るタイミングを管理する。

 このように「クラスが負う責任」ごとに分けておくと、「記憶を複数保存したい」とか「記憶の保存方式を変更したい」といった場合、Caretakerクラスの変更だけで済むようになります。また、記憶として保存したい情報が増えた場合は、Caretakerクラスの週性は必要ありません。

 次に「APIの公開」について見ていきましょう。

 Mementoパターンでは、どのクラスが「記憶」にアクセスできるか、またそれをどのように保証するかが重要になります。本来のMementoパターンでは、Mementoクラスは2種類のアクセス制御を行います。1つは、インスタンス化や内部情報へのアクセスを許可しない「narrowインターフェース」、もうひとつは、それらを許可する「wideインターフェース」で、記憶を作るOriginatorクラスだけに公開します。

 これを実装するには、言語でサポートされているアクセス制御の機構を使うことになります。PHPではバージョン5以降、メソッドやメンバ変数に対してアクセス権を設定できるようになりましたが、特定のクラスに対してのみwideインターフェースを公開することは難しいと思います。

PHP5でのアクセス権
アクセス権内容
publicどこからでもアクセス可能。アクセス権を指定しない場合、publicとなる。
protectedサブクラスや親クラス、それを定義するクラス自体だけにアクセスを許可する
privateそれを定義するクラスのみにアクセスを許可する

 このため、OriginatorクラスとMementoクラスに継承関係を作り、すべてのwideインターフェースをprotectedメソッドとして定義することで、Originatorクラスだけにwideインターフェースを公開することができます。

 ちなみに、C++では特定のクラスどうしを「フレンドクラス」として定義することで、実装できます。また、JavaではPHP5のアクセス権に加え、「パッケージ」の範囲を対象とするアクセス権があります。OriginatorクラスとMementoクラスを同じパッケージとすることで、実装することができます。

関連するパターン

 Mementoパターンと良く併用されるパターンです。

まとめ

 ここではオブジェクトの記憶を保存したり復元したりするMementoパターンについて見てきました。

*1:本来ビジネスロジックに関係ないデータベースに関連する処理を切り離すパターンはDAOパターンとも呼ばれます。

*2:このようなオブジェクトをモック(mock)オブジェクトといいます。

2014-12-17

[][]PHPによるデザインパターン入門 - Mediator〜すべては相談役が知っている

このエントリは、Do You PHP?(www.doyouphp.jp)で公開していたコンテンツを移行/加筆/修正したものです。公開の経緯はこちらをどうぞ。目次はこちらです。サンプルコードを手直ししたものgithubに上げてありますのでそちらもどうぞ。

GoF本における分類

 振る舞い+オブジェクト

はじめに

 ここではMediatorパターンについて説明します。

 「mediator」とは「仲介者」「調停者」という意味です。

 Mediatorパターンは、クラスどうしの複雑なやりとりを一極集中するためのパターンです。

 では、早速見ていきましょう。

たとえば

 突然ですが、みなさんは無線LANをご存じでしょうか?職場や学校、ご自宅でも使っている方は多いと思います。最近のノートパソコンは、まず間違いなく無線LANが使えるようになっていますね。

 それでは、無線LANの接続形態にはどの様なものがあるでしょうか?それは、アドホックモードインフラストラクチャモードです。前者はアクセスポイントを経由しないで、パソコンどうしでデータ通信をおこなうのに対し、後者はアクセスポイントを経由して他のパソコンとデータ通信をおこないます。

 では、話を戻しましょう。オブジェクト指向プログラミングでは、お互いにやりとりをおこなうオブジェクトの集まりとしてプログラミングをおこないます。この「やりとり」を実現する最も簡単な方法は何でしょうか?そう、他のオブジェクトメソッドを直接呼び出すことです。

 しかし、オブジェクトどうしがお互いにやりとりをしてしまっては都合が悪い場合があります。特にオブジェクトの数が増えた場合、オブジェクトどうしのやりとりがあちらこちらで発生することになります。その結果、オブジェクトどうしの関係が分かりにくくなります。また、やりとりのための処理はそれぞれのオブジェクトに存在していますので、非常にメンテナンスしづらい状況に陥ってしまいます。

 これはまさにアドホックモードと同じ状態です。

 ここで、オブジェクトどうしのやりとりを仲介してくれる「仲介者」がいた場合はどうでしょうか?つまり、オブジェクトどうしのやりとりをインフラストラクチャモードにするということです。

 この場合、他のオブジェクトとのやりとりは必ず仲介者を通じておこなうことになり、どのオブジェクトとやりとりをおこなうかは、仲介者が面倒を見るようにします。こうすると、オブジェクトは「どのオブジェクトのどのメソッドを呼び出す」といった難しいことを考える必要はなく、仲介者にお願いするだけで他のオブジェクトとやりとりができます。また、オブジェクトどうしの具体的なやりとりの手順が仲介者に集中しますので、管理も容易になります。

 Mediatorパターンは、このような場面で適用されるパターンです。

Mediatorパターンとは?

 Mediatorパターンはオブジェクトの振る舞いに注目したパターンで、やりとりをおこなうオブジェクトどうしの結びつきを緩めることを目的としています。

 GoF本では、Mediatorパターンの目的は次のように定義されています。

オブジェクト群の相互作用をカプセル化するオブジェクトを定義する。Mediatorパターンは、オブジェクト同士がお互いを明示的に参照し合うことがないようにして、結合度を低めることを促進する。それにより、オブジェクトの相互作用を独立に変えることができるようになる。

 Mediatorパターンは、関連しあうクラス群のやりとりを1つのオブジェクトに集約させ、クラス群のやりとりの仲介者とします。こうすると、クラスどうしのやりとりを一極集中させる、つまり仲介者とクラスの関係を一対多にします。それぞれのクラスは、仲介者のクラスだけを知っていれば、他のクラスを知らなくてもお互いやりとりをおこなうことができます。このため、知らなければならないクラスの数が減ることになり、結果としてクラスどうしの関連を弱くできます。

 また、それぞれのクラスに「どのタイミングでどのクラスのどのメソッドを呼び出す」といった具体的なやりとりの内容を記述する必要がなくなりますので、修正をおこなう場合に大きなメリットとなります。

Mediatorパターンの構造

 Mediatorパターンのクラス図と構成要素は、次のようになります。

f:id:shimooka:20141208191052p:image

 仲介者のクラスです。Colleagueクラスからの通知を受け取るメソッドを定義します。

  • ConcreteMediatorクラス

 Mediatorクラスのサブクラスで、Mediatorクラスで定義されたメソッドを実装します。

  • Colleagueクラス

 Mediatorクラスに通知をおこなうクラスです。「colleague」とは「同僚」「仲間」という意味です。Mediatorクラスからの通知を受け取るメソッドを定義する場合もあります。

  • ConcreteColleagueクラス

 Colleagueクラスのサブクラスです。内部にMediatorオブジェクトを保持しており、このMediatorオブジェクトを通じて他のConcreteColleagueクラスとやりとりをおこないます。

Mediatorパターンのメリット

 Mediatorパターンのメリットとしては、以下のものが挙げられます。

  • クラスどうしのやりとりを一極集中する

 Mediatorパターンを適用すると、クラスどうしのやりとりはMediatorオブジェクトを通じておこなわれるようになります。これにより、やりとりの具体的な方法をMediatorクラスに持たせることができます。

 見方を変えると、それぞれのColleagueオブジェクトどうしがどういった関係なのかが集中する事になり、不明瞭になりがちなColleagueオブジェクトどうしの関係を明確にする助けにもなります。

 Mediatorクラスを通じて他のColleagueオブジェクトにアクセスすることで、Colleagueオブジェクトどうしの結びつきがゆるくなり、MediatorクラスとColleagueクラスの関係は一対多になります。

Mediatorパターンの適用

 Mediatorパターンの適用例を見てみましょう。

 ここでは、複数のユーザがチャットを使ってやりとりをしている模様を表すシンプルなアプリケーションを用意しました。

 まずは、ユーザを表すクラスです。このクラスは、ColleagueクラスとConcreteColleagueクラスの両方に相当します。

Userクラス(User.class.php
<?php
require_once 'Chatroom.class.php';

class User
{
    private $chatroom;
    private $name;
    public function __construct($name)
    {
        $this->name = $name;
    }
    public function getName()
    {
        return $this->name;
    }
    public function setChatroom(Chatroom $value)
    {
         $this->chatroom = $value;
    }
    public function getChatroom()
    {
        return $this->chatroom;
    }
    public function sendMessage($to, $message)
    {
        $this->chatroom->sendMessage($this->name, $to, $message);
    }
    public function receiveMessage($from, $message)
    {
        printf('<font color="008800">%sさんから%sさんへ</font>: %s<hr>', $from, $this->getName(), $message);
    }
}

 特徴的なのは、Userクラスの中にチャットする場を表すChatroomクラスのインスタンスを保持していることです。sendMessageメソッドで他のユーザにメッセージを送信しますが、この時直接そのユーザオブジェクトにアクセスするのではなく、このChatroomオブジェクトに送信しています。

 次は、チャットする場であるChatroomクラスです。このクラスは、MediatorクラスとConcreteMediatorクラスに相当しています。

 Chatroomクラスでは、ユーザーを表すUserオブジェクトを保持し、入室しているユーザを管理しています。

Chatroomクラス(Chatroom.class.php
<?php
require_once 'User.class.php';

class Chatroom
{
    private $users = array();
    public function login(User $user)
    {
        $user->setChatroom($this);
        if (!array_key_exists($user->getName(), $this->users)) {
            $this->users[$user->getName()] = $user;
            printf('<font color="#0000dd">%sさんが入室しました</font><hr>', $user->getName());
        }
    }
    public function sendMessage($from, $to, $message)
    {
        if (array_key_exists($to, $this->users)) {
            $this->users[$to]->receiveMessage($from, $message);
        } else {
            printf('<font color="#dd0000">%sさんは入室していないようです</font><hr>', $to);
        }
    }
}

 また、ユーザどうしのメッセージの流れを管理しています。このアプリケーションでは、受け取ったメッセージを単純にユーザオブジェクトのreceiveMessageメソッドに渡しています。

 次はクライアント側のコードです。

 まず、Chatroomオブジェクトを生成し、それぞれのユーザを入室させています。そして、ユーザーどうしでメッセージのやりとりをおこなっています。

クライアント側コード(mediator_client.php
<?php
require_once 'Chatroom.class.php';
require_once 'User.class.php';

$chatroom = new Chatroom();

$sasaki = new User('佐々木');
$suzuki = new User('鈴木');
$yoshida = new User('吉田');
$kawamura = new User('川村');
$tajima = new User('田島');

$chatroom->login($sasaki);
$chatroom->login($suzuki);
$chatroom->login($yoshida);
$chatroom->login($kawamura);
$chatroom->login($tajima);

$sasaki->sendMessage('鈴木', '来週の予定は?') ;
$suzuki->sendMessage('川村', '秘密です') ;
$yoshida->sendMessage('萩原', '元気ですか?') ;
$tajima->sendMessage('佐々木', 'お邪魔してます') ;
$kawamura->sendMessage('吉田', '私事で恐縮ですが…') ;

 最後にサンプルコードのクラス図を示しておきます。

f:id:shimooka:20141208191050p:image

適用例の実行結果

 Mediatorパターンを適用したサンプルの実行結果は、次のようになります。

f:id:shimooka:20141208191051p:image

Mediatorパターンのオブジェクト指向的要素

 Mediatorパターンはオブジェクトどうしのやりとりを「カプセル化」するパターンです。

 冒頭にも出てきましたが、オブジェクト指向プログラミングでは、お互いにやりとりをおこなうオブジェクトの集まりとしてプログラミングをおこないます。このやりとりしあうオブジェクトの数が少ない場合、その複雑さは問題にならないかもしれません。しかし、オブジェクトの数が少し増えただけでも、お互いの関連の数はあっという間に増えてしまいます。

 Mediatorパターンでは、これを「ColleagueオブジェクトMediatorオブジェクトとやりとりをする」という非常にシンプルな処理に置き換えて、Colleagueオブジェクトどうしの具体的なやりとりやColleagueクラスどうしの関連を隠蔽しています。

関連するパターン

  • Façadeパターン

 Façadeパターンもクラスの集まりを対象にしたパターンです。

 Mediatorパターンはクラスどうしのやりとり自体を抽出しますが、Façadeパターンはサブシステムを利用するためのAPIを抽出するパターンです。また、Mediatorパターンは構成しているオブジェクトと双方向のやりとりをおこなう場合がありますが、Façadeパターンではサブシステムに対して要求を送信する一方向のやりとりになります。

  • Observerパターン

 Mediatorパターンに似たパターンとしてObserverパターンがあります。MediatorパターンはColleagueオブジェクトどうしのやりとりの仲介者を用意します。一方のObserverパターンでは、Observerクラス自身が処理をおこないます。

 また、Observerパターンを適用することでColleagueオブジェクトMediatorオブジェクトを通信させることができます。

まとめ

 ここでは複雑なクラス間のやりとりを一元管理するMediatorパターンについて見てきました。

[][]PHPによるデザインパターン入門 - Interpreter〜言語の文法表現を通訳する

このエントリは、Do You PHP?(www.doyouphp.jp)で公開していたコンテンツを移行/加筆/修正したものです。公開の経緯はこちらをどうぞ。目次はこちらです。サンプルコードを手直ししたものgithubに上げてありますのでそちらもどうぞ。

GoF本における分類

 振る舞い+クラス

はじめに

 ここではInterpreterパターンについて説明します。

 「interpreter」とは「通訳者」「解釈者」という意味ですが、何を「通訳」「解釈」するのでしょうか?それは言語の文法表現です。

 「言語」といっても「ある規則に従った文字列」と捉えると、HTMLXMLなど文字通りの「言語」だけではなく、CSVなどのデータフォーマットも「言語」と言えるでしょう。

 身近な例としてプログラミング言語を考えてみましょう。プログラミング言語には本書で取り上げているPHPを始め、PerlRubyなどのコンパイルが不要な言語と、JavaやCなどのコンパイルが必要な言語があります。どのプログラミング言語を使っても最終的には記述したコードが実行されます。

 ここで、コードが実行される手順を大まかに説明すると、次のようになります。

  1. 記述されたコードの文字を意味のある字句(トークン)に分解する(字句解析
  2. 字句解析の結果を基に文法に従っているかどうかチェックする(構文解析
  3. 中間コードや最終的なコードを生成
  4. コードを実行

 1番目の字句解析と2番目の構文解析がおこなわれた結果は木構造として表すことができ、構文木Abstract Syntax Tree)と呼ばれます。たとえば、「$result = $a / 2 + 3 * $b / 2」の構文木は次のように表せます。

「$result = $a / 2 + 3 * $b / 2」の構文木

 Interpreterパターンは、この得られた構文木を処理するための最適なパターンです。

たとえば

 とある決まった問題がたびたび発生する場合、その問題を文章として表せられると便利なことがあります。これは「ミニ言語」と呼ばれる手法です。

 ミニ言語は比較的古くから使われており、PHPのsprintf関数で指定する「%5d」のような表記や正規表現などがあります。

 Interpreterパターンは、このようなミニ言語を実装する場合に適用されるパターンです。

 たとえば、バッチ処理を考えてみましょう。バッチ処理では、とある決まった基本的な処理を順番に実行したり繰り返し実行して、大きな処理をおこないます。通常、この基本的な処理はOS固有のコマンドですが、「バッチ処理言語」として実装することもできます。この場合、バッチ処理言語固有の規則で処理を記述することになりますが、複雑な処理を1つの命令として記述できたり、具体的なOSや言語に依存しないようになります。

Interpreterパターンとは?

 Interpreterパターンはクラスの振る舞いに注目したパターンで、文法を解析し、その結果を利用して処理をおこなうことを目的としています

 GoF本では、Interpreterパターンの目的は次のように定義されています。

言語に対して、文法表現と、それを使用して文を解釈するインタプリタを一緒に定義する。

 Interpreterパターンでは文法で定義されている規則をクラスとして表し、それに対する振る舞いを併せて定義します。この規則とは、構文木における節や葉に相当します。そして、そのクラスのインスタンスを繋げることで構文木そのものを表現しつつ、構文木を処理します

Interpreterパターンの構造

 Interpreterパターンのクラス図と構成要素は、次のようになります。

f:id:shimooka:20141208190846p:image

  • AbstractExpressionクラス

 構文木の要素に共通なAPIを定義します。

  • TerminalExpressionクラス

 AbstractExpressionクラスのサブクラスで、構文木の葉に相当する末端の規則を表します。また、AbstractExpressionクラスで定義されたメソッドを実装します。

  • NonterminalExpressionクラス

 AbstractExpressionクラスのサブクラスで、構文木の節に相当します。内部には、他の規則へのリンクを保持しています。また、AbstractExpressionクラスで定義されたメソッドを実装します。

  • Contextクラス

 構文木を処理するために必要な情報を保持するクラスです。

 AbstractExpressionクラスを利用するクラスです。処理する構文木を作成したり、外部から与えられたりします。

Interpreterパターンのメリット

 Interpreterパターンのメリットとしては、以下のものが挙げられます。

  • 規則の追加や変更が簡単

 Interpreterパターンの特徴の1つに、「1つの規則を1つのクラスで表す」というものが挙げられます。つまり、新しい規則を追加する場合はAbstractExpressionクラスのサブクラスを追加するだけで良くなります。

 また、規則を修正する場合も、AbstractExpressionクラスのサブクラスを修正するだけです。

Interpreterパターンの適用

 Interpreterパターンの適用例として、簡単なミニ言語を作ってみましょう。

 このミニ言語の文法は、次のように定義しました。

<Job> ::= begin <CommandList>
<CommandList> ::= <Command>* end
<Command> ::= diskspace | date | line

 簡単に文法を説明しますと、この言語は「begin」という文字列で始まります。その間にコマンドの一覧を記述します。また、そのコマンド一覧は0個以上のコマンドで構成されており、「end」という文字列で終わります。コマンドは「diskspace」「date」「line」のいずれかになります。

 この記述方法は、BNF(Backus Naur Form)と呼ばれる記法で、コンピュータ言語の文法を定義する際に使われます。また、RFC(Request for Comment)*1でもよく使われています。

 さて、この文法をInterpreterパターンを使って表すとどうなるでしょうか?

 何となくお気づきの方もいらっしゃるかもしれませんね。そう、BNFで記述された文法の一番左にある「<Job>」「<CommandList>」「<Command>」の単位でクラスが作成されます。そして、そのクラスの中で文法に従っているかを判断し、適切に処理を実行するのです。

 では、早速コードを見ていきましょう。

 まずは、AbstractExpressionクラスに相当するCommandインターフェースからです。

 Commandインターフェースでは、構文木に共通なAPIであるexecuteメソッドを定義しているだけです。

Commandクラス(Command.class.php
<?php
interface Command {
    public function execute(Context $context);
}

 次は、BNFにおける「<Job>」に相当するクラスのJobCommandクラスです。

JobCommandクラス(JobCommand.class.php
<?php
class JobCommand implements Command
{
    public function execute(Context $context)
    {
        if ($context->getCurrentCommand() !== 'begin') {
            throw new RuntimeException('illegal command ' . $context->getCurrentCommand());
        }
        $command_list = new CommandListCommand();
        $command_list->execute($context->next());
    }
}

 executeメソッドに注目してください。このクラスではミニ言語の文法のうち、「<Job>」の部分だけを担当していることが分かりますか?

<Job> ::= begin <CommandList>

 次は、「<CommandList>」に相当するCommandListCommandクラスです。

CommandListCommandクラス(CommandListCommand.class.php
<?php
class CommandListCommand implements Command
{
    public function execute(Context $context)
    {
        while (true) {
            $current_command = $context->getCurrentCommand();
            if (is_null($current_command)) {
                throw new RuntimeException('"end" not found ');
            } elseif ($current_command === 'end') {
                break;
            } else {
                $command = new CommandCommand();
                $command->execute($context);
            }
            $context->next();
        }
    }
}

 コマンドの一覧からコマンドを取り出して1つずつ実行し、「end」が現れるとそこで終了します。このクラスも「<CommandList>」に相当する処理だけをおこなっていますね。

>||<CommandList> ::= <Command>*

||<

 続けてCommandCommandクラスです。このクラスはTerminalExpressionクラス、つまり構文木の「葉」にあたるクラスです

 

CommandCommandクラス(CommandCommand.class.php
<?php
class CommandCommand implements Command
{
    public function execute(Context $context)
    {
        $current_command = $context->getCurrentCommand();
        if ($current_command === 'diskspace') {
            $path = './';
            $free_size = disk_free_space('./');
            $max_size = disk_total_space('./');
            $ratio = $free_size / $max_size * 100;
            echo sprintf('Disk Free : %5.1dMB (%3d%%)<br>',
                         $free_size / 1024 / 1024,
                         $ratio);
        } elseif ($current_command === 'date') {
            echo date('Y/m/d H:i:s') . '<br>';
        } elseif ($current_command === 'line') {
            echo '--------------------<br>';
        } else {
            throw new RuntimeException('invalid command [' . $current_command . ']');
        }
    }
}

 このクラスも「<Command>」に相当する処理だけをおこなっていることを確認してください。

<Command> ::= diskspace | date | line

 さて、ここまで見てきたコードに必要なクラスで、まだ説明していないクラスがあります。構文木の情報を保持するためのContextクラスです。

 このクラスは、現在構文木のどこを解析しているか、つまり、現在解析の対象となっているコマンドや次に出てくるコマンドを管理しています。

Contextクラス(Context.class.php
<?php
class Context
{
    private $commands;
    private $current_index = 0;
    private $max_index = 0;
    public function __construct($command)
    {
        $this->commands = split(' +', trim($command));
        $this->max_index = count($this->commands);
    }

    public function next()
    {
        $this->current_index++;
        return $this;
    }

    public function getCurrentCommand()
    {
        if (!array_key_exists($this->current_index, $this->commands)) {
            return null;
        }
        return trim($this->commands[$this->current_index]);
    }
}

 最後に、入力フォームに入力されたコマンドの実行結果を表示するクライアント側のコードです。

interpreter_clientクラス(interpreter_client.php
<?php
require_once 'Context.class.php';
require_once 'Command.class.php';
require_once 'CommandCommand.class.php';
require_once 'CommandListCommand.class.php';
require_once 'JobCommand.class.php';

function execute($command) {
    $job = new JobCommand();
    try {
        $job->execute(new Context($command));
    } catch (Exception $e) {
        echo htmlspecialchars($e->getMessage(), ENT_QUOTES, mb_internal_encoding());
    }
    echo '<hr>';
}

    $command = (isset($_POST['command'])? $_POST['command'] : '');
    if ($command !== '') {
        execute($command);
    }
?>
<form action="" method="post">
input command:<input type="text" name="command" size="80" value="begin date line diskspace end">
<input type="submit">
</form>

 コマンドの初期値として「begin date line diskspace end」というコマンドを設定していますが、このコマンドの構文木は次のようになります。

「begin date line diskspace end」の構文木

 それでは、Interpreterパターンを適用したサンプルのクラス図を確認しておきましょう。

f:id:shimooka:20141208190844p:image

適用例の実行結果

 Interpreterパターンを適用したサンプルの実行結果は、次のようになります。

f:id:shimooka:20141208190845p:image

Interpreterパターンのオブジェクト指向的要素

 Interpreterパターンは「ポリモーフィズム」を非常に活用したパターンです。

 ここまで見てきたように、AbstractExpressionクラスではそれぞれの規則に共通なAPIを提供しています。そして、AbstractExpressionクラスのサブクラスであるTerminalExpressionクラスやNonterminalExpressionクラスで、それぞれの規則の具体的な処理内容をこのこのAPIに実装しています。

 また、NonterminalExpressionクラスは内部に他の規則、つまりTerminalExpressionオブジェクトもしくはNonterminalExpressionオブジェクトを保持しています。自身の処理によって、内部に保持したオブジェクトに次の処理を依頼します。

 ここで、TerminalExpressionクラスもNonterminalExpressionクラスも同じAbstractExpression型のオブジェクトとして扱うことができます。ということは、内部に保持したオブジェクトに処理を依頼する際、そのオブジェクトがTerminalExpressionオブジェクトなのかNonterminalExpressionオブジェクトなのかを意識する必要がなくなります。

 この「具体的な実装を意識する必要がない」ため、新しい規則を表すクラスを容易に追加できるのです。

関連するパターン

 気づいた方もいるかと思いますが、構文木を形成するAbstractExpressionクラスとそのサブクラスの構造はCompositeパターンと非常に似ています。

 TerminalExpressionクラスにFlyweightパターンが適用される場合があります。

  • Visitorパターン

 構文木の各要素に対する振る舞いを1つのクラスにまとめたい場合、Visitorパターンが利用できます。

まとめ

 ここでは構文木を構成・処理するInterpreterパターンについて見てきました。

 こう見ると、Interpreterパターンは他のGoFパターンと比べても用途が非常に具体的なパターンと言えますね。

[][]PHPによるデザインパターン入門 - Flyweight〜同じものは一度しか作らない

このエントリは、Do You PHP?(www.doyouphp.jp)で公開していたコンテンツを移行/加筆/修正したものです。公開の経緯はこちらをどうぞ。目次はこちらです。サンプルコードを手直ししたものgithubに上げてありますのでそちらもどうぞ。

GoF本における分類

 構造+クラス、オブジェクト

はじめに

 ここではFlyweightパターンについて説明します。

 flyweightと言えば、ボクシングの「フライ級」を連想する人も多いと思いますが、その通りです。Flyweightパターンは、フライ級のオブジェクトを効率よく共有するためのパターンなのです。オブジェクトを共有、つまり使いまわすことでメモリの節約を図ります。

たとえば

 商品情報を格納するクラスを考えてみましょう。

 商品情報には、商品番号や商品名などの「状況によって変わらない情報」と在庫数などの「状況によって変わる情報」があります。

 ある商品を表すオブジェクトを生成するには、この商品クラスをインスタンス化しますが、「状況によって変わらない情報」を持つオブジェクトを毎回インスタンス化するのはもったいないですね。なぜなら、インスタンス化された商品オブジェクトは、それも全く同じ情報を持つものだからです。

 一方で在庫数をこの商品クラスに持たせるのは得策ではありません。なぜなら、インスタンス化したタイミングで在庫数が変わっている可能性があるためです。さもないと、在庫が余りすぎたり足りなかったりしてしまうでしょう。

 また、多くのシステムでは商品情報をデータベースに格納しています。つまり、商品クラスをインスタンス化する際にデータベースから商品情報を取得していることが多くなります。このため、毎回インスタンス化するコストが無視できない場合も出てきます。

 こういった場合、どのような対処をおこなえば良いでしょうか?

 一度インスタンス化した後にそのインスタンスを使いまわした方が良さそうです。そうすれば、インスタンス化のコストを大きく抑えることができます。また、インスタンス化に伴うメモリの消費量も抑えられると期待できます。

 ここで説明するFlyweightパターンは、一度インスタンス化したオブジェクトを使い回し、生成されるオブジェクトの数やリソースの消費を抑えます

Flyweightパターンとは?

 Flyweightパターンは、オブジェクトの構造に注目したパターンで、多くのオブジェクトを効率よく扱うことを目的としています

 GoF本では、Flyweightパターンの目的は次のように定義されています。

多数の細かいオブジェクトを効率よくサポートするために共有を利用する。

 Flyweightパターンでは、生成されるクラスとそのインスタンスを生成・管理するファクトリに分かれています。

 クライアント側でそのクラスのインスタンスが必要になった場合、ファクトリに生成を依頼し、インスタンスを入手します。一方のファクトリ側は、生成したインスタンスを内部に保持します。再び生成の依頼を受けた場合、その保持したインスタンスを返します。つまり、一度生成したインスタンスをそのまま使いまわすわけです。

 こうすることで、必要となったインスタンスだけ作成することになり、使用するメモリや生成時のコストを抑えることができます。

 何か良いことずくめのようですが、Flyweightパターンを適用する場合に注意することがあります。それは、どのオブジェクトを共有して良いか、という点です。

 「情報」には、環境や状況によって変化しないものと、そうでないものがあります。前者は「intrinsic」(「本質的な」の意)、後者は「extrinsic」(「非本質的な」の意)と呼ばれます。

 環境や状況によって変化してしまう情報を、あちらこちらで共有してしまうと、他のすべての共有場所にその影響が出てしまうことになります。たとえば、誕生日は環境や状況によって変化しませんので、intrinsicな情報ということになります。しかし、年齢は時が経てば変わってしまいます。つまり、extrinsicな情報ということになります。

intrinsicな情報とextrinsicな情報
情報の種類意味共有
intrinsic環境や状況によって変化しないできる
extrinsic環境や状況によって変化するできない

 Flyweightパターンで共有するオブジェクトは、intrinsicな情報を持つオブジェクトになります。

 また、GoFパターンではありませんが、ObjectPoolパターンと呼ばれるパターンがあります。Flyweightパターンでオブジェクトの生成を担当するファクトリは、ObjectPoolパターンと良く似ています。ObjectPoolパターンも、オブジェクトを再利用するためのパターンです。

Flyweightパターンの構造

 Flyweightパターンのクラス図と構成要素は、次のようになります。

f:id:shimooka:20141208190723p:image

 共有するオブジェクトの共通APIを定義します。

  • ConcreteFlyweightクラス

 Flyweightクラスのサブクラスで、Flyweightクラスで定義されたAPIを実装するクラスです。このクラスのインスタンス共有されますので、intrinsicな情報のみ保持するようにします。

  • UnsharedConcreteFlyweightクラス

 Flyweightクラスのサブクラスで、Flyweightクラスで定義されたAPIを実装するクラスです。このクラスのインスタンス共有されません。従って、extrinsicな情報を保持しても構いません。

  • FlyweightFactoryクラス

 Flyweight型のインスタンスを生成・保持します。

 Flyweight型のオブジェクトへの参照を保持します。

Flyweightパターンのメリット

 Flyweightパターンのメリットとしては、以下のものが挙げられます。

 Flyweightパターンでは、オブジェクトを共有するパターンです。一度生成したインスタンスを内部に保存しておき、再び生成する必要が生じた場合、その保持したインスタンスを返すようにしています。

 このため、毎回newしてオブジェクトを生成するよりも、実際に生成されるオブジェクトの数を圧倒的に抑えることができます

 冒頭では、使用メモリの節約について説明しました。Flyweightパターンでは、メモリ以外のリソースも節約することができます。たとえば、インスタンスを生成する時間がそうです。インスタンスを生成することは、非常に時間がかかる処理の1つです。Flyweightパターンでは一度生成したオブジェクトを使いまわすことで、使用メモリを節約すると共に、生成にかかる時間も節約します。これによって、プログラムのパフォーマンスを上げることができます。

Flyweightパターンの適用

 では、Flyweightパターンの適用例を見てみましょう。

 ここでは、ファイルに格納された商品情報から商品オブジェクトを生成し、一覧表示するアプリケーションです。また、生成した同じ商品オブジェクトを比較し、同一のオブジェクトかどうかを確認しています。

 まずは、商品情報を格納するItemクラスから見てみます。

 このクラスはコンストラクタに渡された商品情報を内部に保持し、その情報にアクセスするためのメソッドを用意しただけのシンプルなものです。このクラスには特に難しいところはないでしょう。

 このオブジェクトにはintrinsicな情報、つまり、環境や状況によって変化しない情報だけが格納されます。

Itemクラス(Item.class.php
<?php
/**
 * FlyweightとConcreteFlyweightに相当する
 */
class Item
{

    private $code;
    private $name;
    private $price;

    public function __construct($code, $name, $price)
    {
        $this->code = $code;
        $this->name = $name;
        $this->price = $price;
    }

    public function getCode()
    {
        return $this->code;
    }

    public function getName()
    {
        return $this->name;
    }

    public function getPrice()
    {
        return $this->price;
    }

}

 次は、商品オブジェクトを生成・管理するItemFactoryクラスです。このクラスは、Singletonパターンも適用されています。

ItemFactoryクラス(ItemFactory.class.php
<?php
require_once 'Item.class.php';

/**
 * FlyweightFactoryに相当する
 * また、Singletonパターンにもなっている
 *
 * なお、このサンプルではUnsharedConcreteFlyweightオブジェクトを
 * 返すメソッドは用意されていない
 */
class ItemFactory
{
    private $pool;
    private static $instance = null;

    /**
     * コンストラクタ
     * このサンプルでは、インスタンス生成時に保持するオブジェクトを
     * すべて生成している
     */
    private function __construct($filename)
    {
        $this->buildPool($filename);
    }

    /**
     * Factoryのインスタンスを返す
     */
    public static function getInstance($filename) {
        if (is_null(self::$instance)) {
            self::$instance = new ItemFactory($filename);
        }
        return self::$instance;
    }

    /**
     * ConcreteFlyweightを返す
     */
    public function getItem($code)
    {
        if (array_key_exists($code, $this->pool)) {
            return $this->pool[$code];
        } else {
            return null;
        }
    }

    /**
     * データを読み込み、プールを初期化する
     */
    private function buildPool($filename)
    {
        $this->pool = array();

        $fp = fopen($filename, 'r');
        while ($buffer = fgets($fp, 4096)) {
            list($item_code, $item_name, $price) = split("\t", $buffer);
            $this->pool[$item_code] = new Item($item_code, $item_name, $price);
        }
        fclose($fp);
    }

    /**
     * このインスタンスの複製を許可しないようにする
     * @throws RuntimeException
     */
    public final function __clone() {
        throw new RuntimeException ('Clone is not allowed against ' . get_class($this));
    }
}

 このクラスで注目するのは、buildPoolメソッドとgetItemメソッドです。

 buildPoolメソッドはプライベートメソッドとなっており、コンストラクタから呼び出されています。このメソッドでは、ファイルに保存された商品情報を読み込んで商品インスタンスを生成し、そのインスタンスを内部に保持しています。

 一方のgetItemメソッドでは、指定された商品番号のインスタンスをそのまま返しています。これにより、一度生成された商品インスタンスが使いまわされていることが分かると思います。

 なお、商品データファイルは次のとおりで、タブ区切りテキストとなっています。

商品データファイル(data.txt)
ABC0001	限定Tシャツ	3800
ABC0002	ぬいぐるみ	1500
ABC0003	クッキーセット	800

 そして、クライアント側のコードです。

 まずはItemFactoryクラスのインスタンスを取得し、商品番号を指定して商品オブジェクトを取得しています。

 また、商品番号「ABC0001」については、再度インスタンスを取得し、1度目に取得したインスタンスと2度目に取得したインスタンスを比較しています。

クライアント側コード(flyweight_client.class.php
<?php
require_once 'ItemFactory.class.php';

function dumpData($data) {
    echo '<dl>';
    foreach ($data as $object) {
        echo '<dt>' . htmlspecialchars($object->getName(), ENT_QUOTES, mb_internal_encoding()) . '</dt>';
        echo '<dd>商品番号:' . $object->getCode() . '</dd>';
        echo '<dd>\\' . number_format($object->getPrice()) . '-</dd>';
    }
    echo '</dl>';
}

$factory = ItemFactory::getInstance('data.txt');

/**
 * データを取得する
 */
$items = array();
$items[] = $factory->getItem('ABC0001');
$items[] = $factory->getItem('ABC0002');
$items[] = $factory->getItem('ABC0003');

if ($items[0] === $factory->getItem('ABC0001')) {
    echo '同一のオブジェクトです';
} else {
    echo '同一のオブジェクトではありません';
}

dumpData($items);

 最後に、Flyweightパターンを適用したサンプルのクラス図を確認しておきましょう。

f:id:shimooka:20141208190721p:image

適用例の実行結果

 Flyweightパターンを適用したサンプルの実行結果は、次のようになります。

f:id:shimooka:20141208190722p:image

Flyweightパターンのオブジェクト指向的要素

 「オブジェクト指向的な要素」という視点から見ると、Flyweightパターンはちょっと特殊な形をしています。あえて言えば、処理の「カプセル化」を利用しているパターンです。

 Flyweightパターンでは、Flyweight型のオブジェクトを内部に保持して管理しています。同時に、他のクラスからFlyweight型のオブジェクトを取得する方法も提供しています。ただし、一度保持したオブジェクトが再度必要になったとき、新たに生成するのではなく、保持したオブジェクトを返します。この結果、オブジェクトを生成する処理をクライアント側から隠蔽することができます。

関連するパターン

 Compositeパターンは、Flyweightパターンと併用されることが多いパターンです。

  • Stateパターン、Strategyパターン

 StateパターンやStrategyパターンは、Flyweightパターンと併用すると効率的な実装ができる場面が多いパターンです。

まとめ

 ここではたくさんの小さなオブジェクトを共有するFlyweightパターンについて見てきました。

[][]PHPによるデザインパターン入門 - Decorator〜かぶせて機能UP

このエントリは、Do You PHP?(www.doyouphp.jp)で公開していたコンテンツを移行/加筆/修正したものです。公開の経緯はこちらをどうぞ。目次はこちらです。サンプルコードを手直ししたものgithubに上げてありますのでそちらもどうぞ。

GoF本における分類

 構造+オブジェクト

はじめに

 ここではDecoratorパターンについて説明します。

 Decoratorとは「装飾者」という意味があります。そもそも装飾するというのは、基本となる物がありそこに様々な効果をもたらす要素を加えていく行為です。クリスマスツリーに飾りをつけるのもそうですし、アイスクリームにトッピングをすることもそうです。

 Decoratorパターンも同様なイメージとして捉えることができます。つまり、基本となるものに対して様々な機能をひとつひとつ加えていき、あたかも装飾するかのようなイメージです。

たとえば

 世の中には様々なアプリケーションが存在しています。そのほとんどには画面があり、カスタマイズできるものが多くあります。

 たとえば、Webブラウザを考えてみましょう。Webページを表示した時、一画面に収まりきらない場合には横スクロールバーや縦スクロールバーが表示されます。また、縦スクロールバーもしくは横スクロールバーだけ表示される時もありますし、両方表示される場合もあります。

 このスクロールバーは、「Webページを表示する」というWebブラウザの基本的な機能に「画面をスクロールさせる」という機能を追加したものと捉えることができます。その他にも、様々な効果をもたらす機能がブラウザにありますが、いずれもWebブラウザの機能を拡張したものと言えるでしょう。

 さて、それぞれのクラスには様々な機能があります。つまり、「責任」を持っているということです。この責任を追加したい場合、つまり機能を拡張したい場合、どの様にすれば良いでしょうか?

 とっさに思いつくのは、継承を使ってサブクラス化することです。継承を使うことで、親クラスの責任を受け継ぎつつ、新しい責任を追加することができます。しかし、この「継承」が時として不便になってくる場合があります。

 先ほどのスクロールバーの例で考えてみましょう。たとえば、「横スクロールバーは実装しないが、縦スクロールバーは実装したい」や「さらにその逆の実装パターンも存在する」といった要求が出てきた場合、それぞれの機能を実装したクラスが必要になります。

 これは継承による機能拡張は「静的」な拡張だからです。言い換えると、クラスのコードを作成した時点でその責任が決定されるということです。コードを書いた時点で機能が決まってしまうため、当然ですが機能を組み合わせることが非常に難しくなります。

f:id:shimooka:20141208190228p:image

 つまり、すべてのパターンを網羅しようとすると、作成すべきクラスの数が非常に多くなってしまいます。また、新しい機能を追加する場合も、すべてのパターンを網羅するために…これは非常に大変そうです。

 また、「ユーザの操作によってスクロールバーを追加したり取り外したりしたい」といった要求もあるでしょう。継承を利用して機能を拡張した場合、このような要求に応えることも容易ではありません。

 このような場面で、Decoratorパターンが有用になってきます。

Decoratorパターンとは?

 Decoratorパターンはオブジェクトの構造に注目したパターンで、オブジェクトに対して機能を柔軟に追加したり取り外したりすることを目的としています

 GoF本では、Decoratorパターンの目的は次のように定義されています。

オブジェクトに責任を動的に追加する。Decorator パターンは、サブクラス化よりも柔軟な拡張方法を提供する。

 クラスの機能、つまりクラスの責任を拡張するにはサブクラス化、つまり継承によって実現しますが、継承による拡張は、責任を「静的」に追加することを意味します。

 一方、Decoratorパターンでは責任を「動的」に追加することができます。つまり、実行中に責任を追加したり外したりできるということです。これにより、オブジェクトの柔軟な機能拡張が可能です。

Decoratorパターンの構造

 Decoratorパターンのクラス図は次のようになります。

f:id:shimooka:20141208190229p:image

  • Componentクラス

 拡張される機能を定義した抽象クラスです。

  • ConcreteComponentクラス

 Componentクラスで定義した機能を基本実装する、飾り付けされる具象クラスです。

 Componentを継承し、さらにメンバ変数としてComponentを保持する抽象クラスです。

 自身のOperationメソッドではComponentのOperationを呼び出すようにします。つまりOperationメソッドの具体的な実装をComponentへ委譲します。

  • ConcreteDecoratorAクラス、ConcreteDecoratorBクラス

 Componentに機能を追加するためにDecoratorクラスを継承し、自身のOperationメソッドで親クラスのOperationメソッドを利用しながら、機能の拡張(飾り付け)を行います。

Decoratorパターンのメリット

  • 柔軟な拡張が可能

 既存クラスのメソッドに機能を追加する場合、サブクラスを作成し、既存クラスの機能追加したいメソッドをオーバーライドする事が一般的かもしれません。つまり継承です。しかし、追加したい「機能」のパターンが複数ある場合、また、追加したい「機能」は他のパターンを踏まえた上で実装されることがある場合、さらには追加するパターンに「順序」が存在する場合、そのパターンの組み合わせを考慮したクラス設計が必要になります。機能が静的に追加されている状態であり、必要なパターンの組み合わせの数分、サブクラスの作成が必要となります。

 こういった場合に、Decoratorパターンを適応することで、機能を動的に追加できるようになり、オブジェクトを利用する側でパターンの選択ができるようになります。Decoratorパターンは、継承ではなく委譲を利用することにより、柔軟な機能の拡張が可能となっています。

  • 機能の実装を階層構造の上位で定義しなくて済む

 Decoratorパターンは機能の枠を上層で定義(インターフェースなど)し、それに対しての実装をDecoratorで定義できるため、クラス階層の上層で機能を定義する必要がなくなります。こうすることで、再度編集する際に上層のクラスに手を入れる必要がなくなり、メンテナンス性の向上に繋がります。

Decoratorパターンの適用

 Decoratorパターンの適用例を見てみましょう。

 ここでは、入力された文字列を加工し表示するアプリケーションDecoratorパターンを適用してみます。文字列の加工には、以下のような種類を用意しました。

文字列の加工の種類
対応するクラス機能の概要
UpperCaseText入力された文字の半角小文字を半角大文字に変換する
DoubleByteText入力された文字の半角文字を全角文字に変換する

 まずは、Componentクラスに相当するTextインターフェースから見てみましょう。

 このインターフェース文字列の入力、出力のためのメソッドを定義したインターフェースです。

Textインターフェース(Text.class.php
<?php
/**
 * テキストを扱うインターフェースクラスです
 */
interface Text {
    public function getText();
    public function setText($str);
}

 続けて、Textインターフェースを実装したPlainTextクラスです。ConcreteComponentクラスに相当します。

 このクラスは、加工する前の文字列を管理するためのクラスです。

PlainTextクラス(PlainText.class.php
<?php
require_once('Text.class.php');

/**
 * 編集前のテキストを表すクラスです
 */
class PlainText implements Text
{

    /**
     * インスタンスが保持する文字列です
     */
    private $textString = null;

    /**
     * インスタンスが保持する文字列を返します
     */
    public function getText()
    {
        return $this->textString;
    }

    /**
     * インスタンスに文字列をセットします
     */
    public function setText($str)
    {
        $this->textString = $str;
    }
}

 次はDecoratorクラスに相当するTextDecoratorクラスです。

 TextDecoratorクラスは、Textインターフェース継承しさらに、Text型のインスタンスを変数として保持しています。そして、getTextメソッドやsetTextメソッドでは保持しているText型のインスタンスを実行しています。

TextDecoratorクラス(TextDecorator.class.php
<?php
require_once('Text.class.php');

/**
 * Textクラスを修飾するDecoratorです
 */
abstract class TextDecorator implements Text
{

    /**
     * Text型の変数です
     */
    private $text;

    /**
     * インスタンスを生成します
     */
    public function __construct(Text $target)
    {
        $this->text = $target;
    }

    /**
     * インスタンスが保持する文字列を返します
     */
    public function getText()
    {
        return $this->text->getText();
    }

    /**
     * インスタンスに文字列をセットします
     */
    public function setText($str)
    {
        $this->text->setText($str);
    }
}

 そして、次は実際の「decorate」をおこなうクラス群です。

 ConcreteDecoratorクラスに相当するUpperCaseTextクラスとDoubleByteTextクラスは、TextDecoratorを継承するクラスです。

 これらのクラスのgetTextメソッドでは、内部に保持したText型オブジェクトgetTextメソッドを実行します。そして、その戻り値に自分自身が担当する処理を施し、結果を返します。

 UpperCaseTextクラスでは半角小文字を半角大文字に変換、DoubleByteTextクラスでは半角文字を全角文字に変換する処理を、それぞれおこないます。

UpperCaseTextクラス(UpperCaseText.class.php
<?php
require_once('TextDecorator.class.php');

/**
 * TextDecoratorクラスの実装クラスです
 */
class UpperCaseText extends TextDecorator
{

    /**
     * インスタンスを生成します
     */
    public function __construct(Text $target)
    {
        parent::__construct($target);
    }

    /**
     * 半角小文字を半角大文字に変換して返します
     */
    public function getText()
    {
        $str = parent::getText();
        $str = mb_strtoupper($str);
        return $str;
    }
}
DoubleByteTextクラス(DoubleByteText.class.php
<?php
require_once('TextDecorator.class.php');

/**
 * TextDecoratorクラスの実装クラスです
 */
class DoubleByteText extends TextDecorator
{

    /**
     * インスタンスを生成します
     */
    public function __construct(Text $target)
    {
        parent::__construct($target);
    }

    /**
     * テキストを全角文字に変換して返します
     * 半角英字、数字、スペース、カタカナを全角に、
     * 濁点付きの文字を一文字に変換します
     */
    public function getText()
    {
        $str = parent::getText();
        $str = mb_convert_kana($str,"RANSKV");
        return $str;
    }
}

 最後にクライアント側のコードです。簡単に動作を確認できるよう、入力用のHTMLフォームも表示します。

 入力用のHTMLフォームにはテキストボックスと2つのチェックボックスがあります。テキストボックスには任意の文字列を入力します。チェックボックスは、入力した文字列に対する装飾が選べるようになっています。

クライアント側コード(decorator_client.php
<?php
require_once('UpperCaseText.class.php');
require_once('DoubleByteText.class.php');
require_once('PlainText.class.php');

$text = (isset($_POST['text'])? $_POST['text'] : '');
$decorate = (isset($_POST['decorate'])? $_POST['decorate'] : array());
if ($text !== '') {
    $text_object = new PlainText();
    $text_object->setText($text);

    foreach ($decorate as $val) {
        switch ($val) {
            case 'double':
                $text_object = new DoubleByteText($text_object);
                break;
            case 'upper':
                $text_object = new UpperCaseText($text_object);
                break;
            default:
                throw new RuntimeException('invalid decorator');
        }
    }
    echo htmlspecialchars($text_object->getText(), ENT_QUOTES, mb_internal_encoding()) . "<br>";
}
?>
<hr>
<form action="" method="post">
テキスト:<input type="text" name="text"><br>
装飾:<input type="checkbox" name="decorate[]" value="upper">大文字に変換
<input type="checkbox" name="decorate[]" value="double">2バイト文字に変換
<input type="submit">
</form>

 それでは、このサンプルのクラス図を確認しておきましょう。

f:id:shimooka:20141208190224p:image

適用例の実行結果

 Decoratorパターンを適用したサンプルの実行結果です。「大文字に変換」のみ選択した場合は次のようになります。

f:id:shimooka:20141208190227p:image

「2バイト文字に変換」のみ選択した場合は次のようになります。

f:id:shimooka:20141208190225p:image

「大文字に変換」と「2バイト文字に変換」の両方を選択した場合は次のようになります。

f:id:shimooka:20141208190226p:image

Decoratorパターンのオブジェクト指向的要素

 Decoratorパターンは「ポリモーフィズム」を非常に活用したパターンです。

 Decoratorパターンは、責任を「追加する側」のDecoratorクラスが「追加される側」のComponentクラスを包み込んで機能を拡張します。この時、「追加する側」は「追加される側」と同じAPIを持っています。なぜなら、両者ともこの共通APIを定義したComponentクラスを実装しているためです。つまり、Componentクラスの利用側から見ると、責任を「追加する側」と「追加される側」を同一視することができます。言い換えると、責任を「追加する側」のクラスがあろうとなかろうと、また「追加する側」のクラスが具体的にどのConcreteDecoratorクラスかを意識することなく、「追加される側」のクラスを利用できる、ということです。

 また、Decoratorクラスは、Component型のオブジェクトを内部に保持しています。このオブジェクトは「Component型である」というだけで、具体的にどのクラスのインスタンスなのかは分かりません。逆に言うと、Component型のオブジェクトであれば、分け隔てなく扱うことができることを意味しています。ここでも具体的なクラスに依存するのではなく、そのインターフェースに依存する構造が利用されています。

関連するパターン

  • Adapter パターン

 AdapterパターンもDecoratorパターンと同様、オブジェクトを包み込むパターンです。

 Decoratorパターンはオブジェクトを包み込むことで、オブジェクトの責任を変化させるパターンです。一方のAdapterパターンは、オブジェクトを包み込んでそのAPIを変化させるパターンです。

 CompositeパターンはDecoratorパターンの構造と非常に良く似ており、オブジェクトの集約を目的としたパターンです。

  • Strategy パターン

 Strategyパターンもオブジェクトの責任を変えるためのパターンです。

 Decoratorパターンはオブジェクトの責任を変えるために「外側」からアプローチしますが、Strategyパターンは「内側」をごっそり変えるアプローチを採ります。

 Componentクラスが大きく、Decoratorパターンを適用した場合にコストがかかりすぎる場合に使用される場合があります。

まとめ

 ここではオブジェクトの責任を外側から変えるDecoratorパターンについて見てきました。

*1IETFインターネットに関する技術標準を定める団体)が正式に発行する文書

2014-12-16

[][]PHPによるデザインパターン入門 - Composite木構造を表す

このエントリは、Do You PHP?(www.doyouphp.jp)で公開していたコンテンツを移行/加筆/修正したものです。公開の経緯はこちらをどうぞ。目次はこちらです。サンプルコードを手直ししたものgithubに上げてありますのでそちらもどうぞ。

GoF本における分類

 構造+オブジェクト

はじめに

 ここではCompositeパターンについて見ていきましょう。

 「composite」とは「合成物」「混合物」という意味を持ちます。ということは、Compositeパターンは、何かを混ぜるためのパターンなのでしょうか?

 Compositeパターンは、単一のオブジェクトとその集合のどちらも同じように扱えるようにするためのパターンです。つまり、「単一のオブジェクト」と「オブジェクトの集合」を混ぜて、アクセス方法を同じにしてしまうパターンです。

 分かるような分からないような、不思議なパターンですね。では、早速見ていきましょう。

たとえば

 ファイルシステムディレクトリツリーを考えてみましょう。

 Windowsであれば、エクスプローラでツリー状に連なったフォルダを表示できますね。このディレクトリツリーにはフォルダやファイルが含まれていますが、フォルダやファイルに対する新規作成や削除、コピーといった操作は共通です。エクスプローラを使っているときに、「これはフォルダだから、こうやって削除しよう」とか「これはファイルだからこうやってコピーしよう」というように意識しないで操作しているはずです。

 また、フォルダはその下にフォルダを含む場合がありますね。場合によっては、フォルダの階層が何階層にもなることもあるでしょう。また、ファイルはフォルダに含まれている、とも言えるでしょう。しかし、これら場合も特に意識しないでコピーや削除といった操作をおこなうことができます。

 これをオブジェクト指向的に考えてみると、「フォルダ」や「ファイル」はそれぞれクラスと考えることができます。具体的なフォルダやファイルは、それぞれのクラスのインスタンスになるでしょう。

 そして、「フォルダ」や「ファイル」に対する操作は、それぞれのメソッドとして定義できそうです。たとえば、フォルダクラスの削除メソッドを呼び出すとフォルダが削除される、といった具合です。また、ファイルクラスの削除メソッドを呼び出した場合はファイルが削除されなければなりませんね。ついでに、フォルダかファイルかを意識しないで操作できれば、利用する側は非常に便利になりそうです。

 あとは、どうやってツリー状に組み上げれば良いかという問題が残っています。何となく予想がつきましたか?そう、フォルダオブジェクトの内部に別のオブジェクトを持たせてやることで再帰的なツリーが表現できそうですね。

 ここまでいろいろと考えてきましたが、何となくでもイメージできたでしょうか?実は、これがCompositeパターンなのです。

Compositeパターンとは?

 Compositeパターンはオブジェクトの構造に注目したパターンで、単体のオブジェクトオブジェクトの集合を同一視することを目的としています。

 GoF本では、Compositeパターンの目的は次のように定義されています。

部分-全体階層を表現するために、オブジェクト木構造に組み立てる。Compositeパターンにより、クライアントは、個々のオブジェクトオブジェクトを合成したものを一様に扱うことができるようになる。

 Compositeパターンでは、オブジェクト木構造に組み立てるのが特徴です。この木構造は、ファイルシステム上のディレクトリツリーをイメージするとよく分かります。逆さにすると、枝葉が延びるような形になっていますね。

 Compositeパターンでは、親子関係を持つオブジェクト再帰的に保持することで木構造を組み立てます。また、任意の枝の部分や末端の葉の部分に対して、共通の手順でアクセスできるような仕組みを提供しています。つまり、単一のオブジェクトにも、複数のオブジェクトから形成されたオブジェクトにも、同じ手順でアクセスできるAPIを提供します。

Compositeパターンの構成要素

 Compositeパターンの構成要素は、次のとおりです。

f:id:shimooka:20141208190110p:image

  • Componentクラス

 Clientクラスに対して、共通にアクセスさせるためのAPIを提供します。このAPIには、子に相当するオブジェクトにアクセスしたり、追加・削除するためのAPIも含まれます。

 Componentクラスのサブクラスの1つです。このクラスは、木構造の末端に位置する葉に相当するクラスです。このクラスは、子に相当するオブジェクトを持ちません。

 Leafクラスと同様、Componentクラスのサブクラスの1つです。木構造の中で、任意枝に相当するクラスです。このクラスは、子に相当するオブジェクトを持ちます。また、Componentクラスで定義された子オブジェクトへのアクセスAPIや追加・削除APIなども実装します。

 ComponentクラスのAPIを通して、木構造にアクセスします。

Compositeパターンのメリット

 Compositeパターンのメリットとしては、以下のものが挙げられます。

 クライアントは、単一のオブジェクト、もしくは複数のオブジェクトから形成されたオブジェクトに同じAPIでアクセスできます。また、木構造の枝の部分だろうが、葉の部分だろうが、同じAPIでアクセスできます。

 通常ですと、今対象としているオブジェクトが枝に相当するのか葉に相当するのかを意識する必要があります。Compositeパターンを適用することで、アクセスする手順が統一されます。

  • 新しい枝を簡単に追加できる

 Compositeパターンでは、木構造の枝葉を同一視する事ができるため、新しい枝(Compositeクラス)を追加するのが非常に簡単です。また、他のComponentクラスやLeafクラスを修正する必要はありません。

Compositeパターンの適用

 Compositeパターンの適用例を見てみましょう。

 ここでは、組織とそれに所属する社員のデータを表示するサンプルです。Compositeパターンを使って組織と社員を同一視している事を意識しながら見てください。

 まずは、Componentクラスに相当するOrganizationEntryクラスです。ここでは抽象クラスとして定義しています。OrganizationEntryクラスには組織と社員に共通なAPIであるgetCodeメソッドとgetNameメソッドが定義されています。

 また、抽象メソッドとしてaddメソッドが定義されています。このメソッド再帰的な構造を作るために必要です。引数はOrganizationEntry型のオブジェクトで、この型がポイントになります。これについては、次のGroupクラスで説明していますので確認してくださいね。

 もうひとつ、データを出力するdumpメソッドが実装されています。これはデフォルトの実装として用意してあります。

OrganizationEntryクラス(OrganizationEntry.class.php
<?php
/**
 * Componentクラスに相当する
 */
abstract class OrganizationEntry
{

    private $code;
    private $name;

    public function __construct($code, $name)
    {
        $this->code = $code;
        $this->name = $name;
    }

    public function getCode()
    {
        return $this->code;
    }

    public function getName()
    {
        return $this->name;
    }

    /**
     * 子要素を追加する
     * ここでは抽象メソッドとして用意
     */
    public abstract function add(OrganizationEntry $entry);

    /**
     * 組織ツリーを表示する
     * サンプルでは、デフォルトの実装を用意
     */
    public function dump()
    {
        echo $this->code . ":" . $this->name . "<br>\n";
    }
}

 続いて、Componentクラスを継承したクラスです。組織を表すGroupクラスはCompositeクラスとして、社員を表すEmployeeクラスはLeafクラスとなります。両クラスともOrganizationEntryクラスを継承し、OrganizationEntryクラスのaddメソッドの具体的な実装をおこなっています。

 ここで、両者に実装の違いがあります。

 組織を表すGroupクラスでは、それに属する組織や社員を追加できるよう、内部の配列にarray_push関数を使って保持するようになっています。この時、追加する内容は、OrganizationEntry型のオブジェクトです。つまり、OrganizationEntryクラスを継承しているGroupクラス、EmployeeクラスはいずれもOrganizationEntry型と言えますので、特に意識することなく、いずれのオブジェクト引数として渡せるとことになります。

 また、逆説的に内部に保持されるオブジェクトは、必ずOrganizationEntry型になることが保証されます。ここでdumpメソッドのforeach文を見てください。内部に保持した配列から1つずつオブジェクトを取り出して、そのdumpメソッドを呼び出していますね?配列の中身がどういった型のオブジェクトか分からない場合、実際にdumpメソッドがあるかどうかをチェックする必要がありますが、ここでは一切チェックをおこなっていません。これは先の説明のとおり、配列の中身が全てOrganizationEntry型のオブジェクトであることが保証されている、つまり必ずdumpメソッドが存在しているからこそ可能になっています。

Groupクラス(Group.class.php
<?php
require_once 'OrganizationEntry.class.php';

/**
 * Compositeクラスに相当する
 */
class Group extends OrganizationEntry
{

    private $entries;

    public function __construct($code, $name)
    {
        parent::__construct($code, $name);
        $this->entries = array();
    }

    /**
     * 子要素を追加する
     */
    public function add(OrganizationEntry $entry)
    {
        array_push($this->entries, $entry);
    }

    /**
     * 組織ツリーを表示する
     * 自分自身と保持している子要素を表示
     */
    public function dump()
    {
        parent::dump();
        foreach ($this->entries as $entry) {
            $entry->dump();
        }
    }
}

 一方、社員を表すEmployeeクラスを見てみましょう。「社員」というものは「社員」自身に属する組織や社員を持つことはできません。ですので、ここではaddメソッドを呼び出した場合に例外を投げるよう実装しています。

Employeeクラス(Employee.class.php
<?php
require_once 'OrganizationEntry.class.php';

/**
 * Leafクラスに相当する
 */
class Employee extends OrganizationEntry
{

    public function __construct($code, $name)
    {
        parent::__construct($code, $name);
    }

    /**
     * 子要素を追加する
     * Leafクラスは子要素を持たないので、例外を発生させている
     */
    public function add(OrganizationEntry $entry)
    {
        throw new Exception('method not allowed');
    }
}

 最後に、説明してきたクラス群を利用するクライアント側のコードです。

 まず、組織の木構造を作っていますが、addメソッド引数に注目してください。GroupオブジェクトだろうがEmployeeオブジェクトだろうが、構わず引数に指定されているのが分かると思います。

クライアント側コード(composite_client.php
<?php
require_once 'Group.class.php';
require_once 'Employee.class.php';

/**
 * 木構造を作成
 */
$root_entry = new Group("001", "本社");
$root_entry->add(new Employee("00101", "CEO"));
$root_entry->add(new Employee("00102", "CTO"));

$group1 = new Group("010", "○○支店");
$group1->add(new Employee("01001", "支店長"));
$group1->add(new Employee("01002", "佐々木"));
$group1->add(new Employee("01003", "鈴木"));
$group1->add(new Employee("01003", "吉田"));

$group2 = new Group("110", "△△営業所");
$group2->add(new Employee("11001", "川村"));
$group1->add($group2);
$root_entry->add($group1);

$group3 = new Group("020", "××支店");
$group3->add(new Employee("02001", "萩原"));
$group3->add(new Employee("02002", "田島"));
$group3->add(new Employee("02002", "白井"));
$root_entry->add($group3);

/**
 * 木構造をダンプ
 */
$root_entry->dump();

 最後にサンプルコードのクラス図を示しておきます。

f:id:shimooka:20141208190108p:image

適用例の実行結果

 Compositeパターンを適用したサンプルの実行結果は、次のようになります。

f:id:shimooka:20141208190109p:image

Compositeパターンのオブジェクト指向的要素

 Compositeパターンは「ポリモーフィズム」を非常に活用したパターンです。

 Componentクラスでは、Clientクラスに対して共通にアクセスさせるためのAPIを提供しています。また、Compositeクラスはごにょごにょと処理を実行し、CompositeクラスやLeafクラスのインスタンスを、Clientクラスに適切に返します。これらのインスタンスは、いずれもComposite型であるところがポイントです。Clientクラスは、返されたインスタンスが、Compositeクラスのインスタンスなのか、Leafクラスのインスタンスなのか分かりません。しかし、Component型であることは分かっています。Clientクラスでは、Componentクラスで提供されたAPIだけを使ってプログラミングすることで、Componentクラスの向こうにあるCompositeクラスやLeafクラスを意識することなく、同一視することができます。

 また、ComponentクラスのサブクラスであるCompositeクラスでは、内部にComponent型のオブジェクトを保持しています。このオブジェクトが、自分自身の子に相当するオブジェクトになります。ここでも、このオブジェクトは「Component型である」というだけで、具体的にCompositeクラスのインスタンスなのか、Leafクラスのインスタンスなのかは分かりません。

 何となく気づきましたか?そうです。Compositeクラスで実装する「子に対するアクセスメソッド」は、Componentクラスが提供しているAPIだけを使うことで、ここでも枝葉の同一視をしています。ClientクラスとComponentクラスの関係と同じですね。

関連するパターン

  • Chain of Responsibilityパターン

 Chain of Responsibilityパターンもオブジェクトどうしのつながりを持っているパターンです。

  • Commandパターン

 複数のコマンドを組み合わせ、大きなコマンドを作る場合にCompositeパターンが使われます。

 Compositeパターンと良く併用されるパターンです。

まとめ

 ここでは「単一のオブジェクト」と「オブジェクトの集合」を同一視するCompositeパターンについて見てきました。

[][]PHPによるデザインパターン入門 - Command〜要求をクラスで表す

このエントリは、Do You PHP?(www.doyouphp.jp)で公開していたコンテンツを移行/加筆/修正したものです。公開の経緯はこちらをどうぞ。目次はこちらです。サンプルコードを手直ししたものgithubに上げてありますのでそちらもどうぞ。

GoF本における分類

 振る舞い+オブジェクト

はじめに

 ここではCommandパターンについて説明します。

 commandという単語は「命令」という意味ですね。「コマンド」と書くと、DOSプロンプトやUNIXLinuxのコンソールで入力するコマンドを連想される方も多いかと思います。

 Commandパターンはその名の通り「命令」そのものをクラスとして表すパターンです。しかし、「命令」をクラスにするとどの様なことになるのでしょうか?

たとえば

 ほとんどのアプリケーションでは、利用者アプリケーションに何らかの要求を出し、アプリケーションは要求を受け取って処理を実行しています。先に出てきたDOSLinuxなどのコマンドもその1つですし、WindowsX WindowといったGUIでの操作も含まれます。

 たとえば、ファイルを圧縮する場合を考えてみましょう。Windowsの場合、圧縮ソフトにファイルをドラッグアンドドロップするといった操作になりますし、Linuxなどのコンソールから実行する場合、圧縮コマンドにファイル名を指定して実行するでしょう。

f:id:shimooka:20141208185557p:image

 この時、利用者ソフトウェアやコマンドを通じて「圧縮する」という要求を出し、対象のファイルがその要求を受け取っている、と考えることができます。また、ファイルに別の操作をおこなう、つまり別な要求を出す場合は、「処理をおこなうソフトやコマンド」を差し替えたものと考えられるでしょう。

 オブジェクト指向プログラミングにおいて、「要求を送る」ということはオブジェクトメソッドを呼び出すということになります。しかし、要求が複雑になったり要求の種類が多くなると、その実装にも限界が出てきますし保守性も悪くなってしまいます。

 そこで、「要求を送る」「要求を受け取る」という考えに基づいて、「要求」自身をクラスとしてまとめてしまうとどうでしょうか?

 そうすると、具体的な要求を1つのオブジェクトとして扱うことができ、複雑な要求の場合でも容易に扱えるようになります。また、送る要求オブジェクトを変えることで、異なる要求を実現こともできそうです。

 Commandパターンは、このような特徴を持っているパターンです。

Commandパターンとは?

 Commandパターンはオブジェクトの振る舞いに注目したパターンで、「要求」そのものをクラスとして表し、「要求を送る側」と「要求を受け取る側」を分離することを目的としています。

 GoF本では、Commandパターンは以下のように定義されています。

要求をオブジェクトとしてカプセル化することによって、様々な要求または要求からなるキューやログによりクライアントパラメータ化する。そして、取り消し可能な操作をサポートする。

 Commandパターンでは、異なる種類の要求に対する処理を同じAPIを持つクラスとして実装します。その結果、処理クラスのインスタンスを切り替えるだけで、様々な要求に対する処理を実行できるようなります。また、Commandパターンを適用すると、新しい要求に対する処理クラスを実装するだけで、既存のクラスを修正することなく対応可能になります。

Commandパターンの構造

 Commandパターンのクラス図と構成要素は、次のようになります。

f:id:shimooka:20141208185559p:image

  • Commandクラス

 命令を実行するためのAPIを定義します

  • ConcreteCommandクラス

 Commandクラスのサブクラスで、Commandクラスで定義されたAPIを実装します。

  • Invokerクラス

 命令実行の要求を出すクラスです。

  • Receiverクラス

 命令をどの様に実行するかを知っている唯一のクラスです。任意のクラスがReceiverクラスになることができます。

Commandパターンのメリット

 Commandパターンのメリットとしては、次のものが挙げられます。

  • 既存のコードを修正することなく機能拡張できる

 Commandパターンを適用すると、要求の受付と要求に対応する処理を切り離して実装できます。その結果、新しい要求が追加された場合でも既存のクラスを修正する必要がなく、追加された要求を処理するためのクラスを実装するだけで済みます。

  • クラスの再利用性を向上させる

 命令そのものが独立したクラスとして実装されますので、他のアプリケーションでの再利用がしやすくなります。

  • 処理のキューイング

 要求と実際の実行を別のタイミングで実施することができるようになります。

  • UndoやRedoのサポート

 Commandクラスに実行したコマンド結果を保持しておくことで、Undo機能やRedo機能を実現することができます。

Commandパターンの適用

 早速、Commandパターンを適用してみましょう。

 ここでは、ファイルの作成・圧縮・コピーをおこなうアプリケーションを用意しました。Commandパターンを適用し、ファイルに対する操作をコマンドとして定義しています。

 まずは、Fileクラスから見ていきます。FileクラスはReceiverクラスに相当するクラスです。作成・圧縮・コピーそれぞれの処理に対するメソッドがありますが、今回はメッセージを表示するだけの実装としています。

Fileクラス(File.class.php
<?php
/**
 * Receiverクラスに相当する
 */
class File
{
    private $name;
    public function __construct($name)
    {
        $this->name = $name;
    }
    public function getName()
    {
        return $this->name;
    }
    public function decompress()
    {
        echo $this->name . 'を展開しました<br>';
    }
    public function compress()
    {
        echo $this->name . 'を圧縮しました<br>';
    }
    public function create()
    {
        echo $this->name . 'を作成しました<br>';
    }
}

 続いて、要求を処理する側のクラス群を見ていきましょう。

 Commandインターフェースは、すべてのコマンドに共通のAPIであるexecuteメソッドを宣言しています。

Commandインターフェース(Command.class.php
<?php
/**
 * Commandクラスに相当する
 */
interface Command {
    public function execute();
}

 このCommandインターフェースを実装したクラスが、TouchCommandクラス、CompressCommandクラス、CopyCommandクラスです。ConcreteCommandクラスに相当し、Commandインターフェースで宣言されたexecuteメソッドを実装しています。

 これらのクラスは、コンストラクタでFileオブジェクトを受け取り、executeメソッドでそれぞれの要求に対する処理をおこないますが、具体的な処理は受け取ったFileオブジェクトに任せています。

TouchCommandクラス(TouchCommand.class.php
<?php
require_once 'Command.class.php';
require_once 'File.class.php';

/**
 * ConcreteCommandクラスに相当する
 */
class TouchCommand implements Command
{
    private $file;
    public function __construct(File $file)
    {
        $this->file = $file;
    }
    public function execute()
    {
        $this->file->create();
    }
}
CompressCommandクラス(CompressCommand.class.php
<?php
require_once 'Command.class.php';
require_once 'File.class.php';

/**
 * ConcreteCommandクラスに相当する
 */
class CompressCommand implements Command
{
    private $file;
    public function __construct(File $file)
    {
        $this->file = $file;
    }
    public function execute()
    {
        $this->file->compress();
    }
}
CopyCommandクラス(CopyCommand.class.php
<?php
require_once 'Command.class.php';
require_once 'File.class.php';

/**
 * ConcreteCommandクラスに相当する
 */
class CopyCommand implements Command
{
    private $file;
    public function __construct(File $file)
    {
        $this->file = $file;
    }
    public function execute()
    {
        $file = new File('copy_of_' . $this->file->getName());
        $file->create();
    }
}

 QueueクラスはCommandオブジェクトを保持するInvokerクラスに相当するクラスで、実際にCommandオブジェクトを実行します。runメソッドを呼び出すと、内部に保持したCommandオブジェクトを順に実行します。

Queueクラス(Queue.class.php
<?php
require_once 'Command.class.php';

/**
 * Invokerクラスに相当する
 */
class Queue
{
    private $commands;
    private $current_index;
    public function __construct()
    {
        $this->commands = array();
        $this->current_index = 0;
    }
    public function addCommand(Command $command)
    {
        $this->commands[] = $command;
    }

    public function run()
    {
        while (!is_null($command = $this->next())) {
            $command->execute();
        }
    }

    private function next()
    {
        if (count($this->commands) === 0 ||
            count($this->commands) <= $this->current_index) {
            return null;
        } else {
            return $this->commands[$this->current_index++];
        }
    }
}

 次は、これまで説明してきたクラス群を利用するクライアント側のコードです。

 まず、Queueオブジェクトを生成した後、addCommandメソッドを使ってファイルに対する要求を表すCommandオブジェクトを追加しています。そして、最後に追加したCommandオブジェクトを実行しています。

クライアント側コード(command_client.php
<?php
require_once 'Queue.class.php';
require_once 'TouchCommand.class.php';
require_once 'CompressCommand.class.php';
require_once 'CopyCommand.class.php';
require_once 'File.class.php';

$queue = new Queue();
$file = new File("sample.txt");
$queue->addCommand(new TouchCommand($file));
$queue->addCommand(new CompressCommand($file));
$queue->addCommand(new CopyCommand($file));

$queue->run();

 ここで、それぞれの要求が実行される様子をシーケンス図を使って確認しておきましょう。

 Invokerクラスに相当するQueueオブジェクトが各ConcreteCommandオブジェクトを実行し、要求の受け取り側のFileオブジェクトにアクセスしている様子が分かりますね。

f:id:shimooka:20141208185904p:image

 最後に、このサンプルアプリケーションのクラス図となります。

f:id:shimooka:20141208185556p:image

適用例の実行結果

 Commandパターンを適用したサンプルの実行結果は、次のようになります。

f:id:shimooka:20141208185558p:image

Commandパターンのオブジェクト指向的要素

 Commandパターンは、「ポリモーフィズム」を利用したパターンになります。

 具体的な「要求」は、CommandクラスのサブクラスであるConcreteCommandクラスで表されます。また、ConcreteCommandクラスではCommandクラスで定義されたAPIを具体的に実装しています。

 また、Invokerクラスは内部にCommand型のオブジェクトを保持し、それらを実行します。この時、これらのオブジェクトはあくまでCommand型として扱われています。つまり、具体的なConcreteCommandクラスに依存していないのです。

 多くのデザインパターンでもポリモーフィズムを使って具体的なクラスを差し替え可能になっています(「交換可能性」といいます)。Commandパターンでは、変化する可能性がある「要求」にポリモーフィズムを使うことで、異なる要求を容易に差し替えられるようにしています。

関連するパターン

 複数のコマンドをまとめたマクロコマンドを実現するために利用されます。

 コマンドの実行履歴を管理するために、Mementoパターンが利用されることがあります。

 Commandクラスを複製したい場合にPrototypeパターンが利用されることがあります。

まとめ

 ここでは「要求」そのものをクラスとして表し、「要求を送る側」と「要求を受け取る側」を分離するCommandパターンについて見てきました。

[][]PHPによるデザインパターン入門 - Chain of Responsibility〜処理のたらい回し

このエントリは、Do You PHP?(www.doyouphp.jp)で公開していたコンテンツを移行/加筆/修正したものです。公開の経緯はこちらをどうぞ。目次はこちらです。サンプルコードを手直ししたものgithubに上げてありますのでそちらもどうぞ。

GoF本における分類

 振る舞い+オブジェクト

はじめに

 ここではChain of Responsibilityパターンについて説明します。

 「Chain of Responsibility」とは長い名前ですね。直訳すると、「責任の鎖」となるでしょうか。

 それぞれのクラスは「責任」を持っています。その責任を明確にするよう設計をおこなうことが、オブジェクト指向設計では大きなポイントとなります。

 Chain of Responsibilityパターンは、自分の責任で対処する必要かどうかを判断し、自分で対処できない場合は他に任せてしまうパターンです。まるで、責任のたらい回しみたいですね。

 では、早速見ていきましょう。

たとえば

 条件によっておこなう処理を分岐させることはよくあることですね。たいていの場合、if文やswitch文を使って「この条件の場合はこう処理する」というコードを記述することになります。

 ここで、入力された文字列を検証する場合を考えてみましょう。

 入力値の検証には、文字列の長さのチェックやあるフォーマットに合っているかどうかのチェックがありますね。

 一般的な検証処理の流れとしては、入力された文字列が所定のパターンにマッチするかどうかを判定し、マッチしない場合は適切なメッセージを表示する、というものになるかと思います。

 この処理をif文を使って実装する場合、マッチングをおこなうパターンの数だけif文が繋がることになるでしょう。しかし、パターンの数が多い場合やマッチングの条件が複雑な場合、コードの見通しが非常に悪くなってしまいがちです。また、コードの再利用がしにくい状態になります。

 if文やswitch文を使った条件分岐は、「どの場合にどう処理をすべきか」が全て1カ所にまとめられることになります。つまり、条件とそれに対応する処理の組を知っておく必要がある、ということです。このため、条件が複雑になったり分岐の数が多くなればなるほど、「知っておかなければならないこと」が増えてしまいます。

 この場合、分岐の条件とそれに対応する処理の組ごとに分解できると、組ごとにその条件や処理内容に集中することができます。その結果、コードの見通しも良くなり、保守性や再利用性を高めることができそうです。

 しかし、条件と処理の組に分解した場合、それらをどうやって組み立てて利用するかが問題になってしまいますね。分解してしまった分、扱いが大変になってしまうと分解した意味がありません。

 こうした問題を解決するためのパターンとして、Chain of Responsibilityパターンがあります。

Chain of Responsibilityパターンとは?

 Chain of Responsibilityパターンはオブジェクトの振る舞いに注目したパターンで、「処理を依頼する側」と「実際に処理をおこなう側」を分離することを目的としています。

 GoF本では、Chain of Responsibilityパターンの目的は次のように定義されています。

1つ以上のオブジェクトに要求を処理する機会を与えることにより、要求を送信するオブジェクトと受信するオブジェクトの結合を避ける。受信する複数のオブジェクトをチェーン状につなぎ、あるオブジェクトがその要求を処理するまで、そのチェーンに沿って要求を渡していく。

 まず、Chain of Responsibilityパターンの特徴である処理をおこなう「オブジェクトのチェーン」について説明しましょう。

 このオブジェクトは、処理をおこなうための共通のAPIを持ち、それぞれ異なる処理を実装しています。この処理はif文などを使った場合に記述する「この条件の場合におこなう処理」になります。

 また、内部に別の処理をおこなうオブジェクトを保持していて、自分が処理できないと判断した場合、そのオブジェクトに処理をお願いします。何だかバケツリレーに似ていますね。「要求」が入ったバケツを受け取り、自分自身が処理できない場合、次の人に渡して処理をお願いする、といった感じです。

 一方、クライアント側は、処理オブジェクトのチェーンに要求を送信するだけです。あとは、その要求が処理オブジェクトのチェーンを伝わっていき、適切に処理可能なオブジェクトが処理を行います。これが、「実際に処理を実行するオブジェクトを動的に決定する」ということです。

 では、どの処理オブジェクトも処理できないことはないのでしょうか?

 残念ながら、処理できない場合も当然あります。これはチェーンが正しく作成されていない場合も同様で、チェーンの終端で要求が消滅してしまう、といったことも起こり得ます。つまり、処理オブジェクトのチェーンで必ず処理されるわけではない事に、注意が必要です。

Chain of Responsibilityパターンの構造

 Chain of Responsibilityパターンのクラス図と構成要素は、次のとおりです。

f:id:shimooka:20141208185401p:image

  • Handlerクラス

 処理オブジェクトの親クラスに相当し、要求を処理するためのAPIを定義します。これは、サブクラスで具体的な処理が実装されます。

 また、内部にHandler型のオブジェクトを保持します。自分が処理できなかった場合、このHandler型のオブジェクトに処理をお願いすることになります。

  • ConcreteHandlerクラス

 Handlerクラスのサブクラスです。処理オブジェクトの実クラスに相当します。このクラスは、Handlerクラスで定義されたAPIを実装します。なお、自分が担当する処理だけが実装されます。

 チェーンを構成しているConcreteHandlerクラスに処理を送信します。

Chain of Responsibilityパターンのメリット

 Chain of Responsibilityパターンのメリットとしては、以下のものが挙げられます。

  • 要求の送信側と受信側の結びつきをゆるくする

 Chain of Responsibilityパターンでは、要求の送信側(Clientクラス)で「どのオブジェクトに処理を行わせるか」ということを意識する必要がありません。「要求が適切に処理される」という事だけを知っていれば良いことになります。

 結果として、Chain of Responsibilityパターンは、オブジェクトどうしの結びつきを緩めることができます。

  • 新しい処理クラスを簡単に追加できる

 要求の送信側と受信側の結びつきがゆるくなるため、新しい処理クラス(ConcreteHandlerクラス)を追加するのが非常に簡単です。

  • 動的に処理チェーンを変更できる

 処理オブジェクトのチェーンは、オブジェクトを繋げたものです。つまり、継承関係のようにプログラミング時に関係が決まるような静的な関連はありません。また、すべての処理オブジェクトは、具体的にはHandler型のオブジェクトです。このため、実行時にオブジェクトを抜き差しすることで、チェーンを動的に変更できます。

 たとえば、ユーザ操作によって処理を変更する場合でも、処理オブジェクトを組み替えたり、追加したりできます。

Chain of Responsibilityパターンの適用

 Chain of Responsibilityパターンの適用例を見てみましょう。

 ここでは、先に出てきた入力された文字列の検証にChain of Responsibilityパターンを適用した例になります。

 まずは「条件とそれに対応する処理の組」に関連するクラスから見ていきます。

 ValidationHandlerクラスはHandlerクラスに相当するクラスです。ここでは、抽象クラスとして定義しています。

 また、実際の検証処理をおこなうexecValidationメソッドエラーメッセージを取り出すgetErrorMessageメソッドは抽象メソッドになっています。この2つのメソッドが「条件判断」と「対応する処理」をおこなうメソッドになっており、このValidationHandlerクラスを継承したクラスで具体的な実装をおこなうことになります。

ValidationHandlerクラス(ValidationHandler.class.php
<?php
/**
 * Handlerクラスに相当する
 */
abstract class ValidationHandler
{

    private $next_handler;

    public function __construct()
    {
        $this->next_handler = null;
    }

    public function setHandler(ValidationHandler $handler)
    {
        $this->next_handler = $handler;
        return $this;
    }

    public function getNextHandler()
    {
        return $this->next_handler;
    }

    /**
     * チェーンの実行
     */
    public function validate($input)
    {
        $result = $this->execValidation($input);
        if (!$result) {
            return $this->getErrorMessage();
        } elseif (!is_null($this->getNextHandler())) {
            return $this->getNextHandler()->validate($input);
        } else {
            return true;
        }
    }

    /**
     * 自クラスが担当する処理を実行
     */
    protected abstract function execValidation($input);

    /**
     * 処理失敗時のメッセージを取得する
     */
    protected abstract function getErrorMessage();
}

 このクラスで注目するのはvalidateメソッドです。このメソッドが、処理オブジェクトの鎖にクライアントからの要求を流す役割を果たします。

 具体的には、execValidationメソッドを呼び出して実際に処理をおこない、処理できたかどうかを判断します。成功した場合は、内部に保持したValidationHandlerオブジェクトを取り出し、次の検証処理をおこないます。失敗した場合は、エラーメッセージクライアントに返します。

 最終的に全てのValidationHandlerオブジェクトで検証をおこない、全ての処理に成功した場合はtrueを返します。

 次にValidationHandlerクラスを継承したクラスたちを見ていきましょう。ここでは検証のパターンとして4つほど用意しています。

 まず、AlphabetValidationHandlerクラスとNumberValidationHandlerクラスです。このクラスたちは、名前の通り入力された文字列がアルファベット、もしくは数字だけで構成されているかどうかを検証します。指定された文字以外で構成されている場合、検証失敗となります。処理の詳細は、それぞれのクラスのexecValidationメソッドとgetErrorMessageメソッドを確認してください。

AlphabetValidationHandlerクラス(AlphabetValidationHandler.class.php
<?php
require_once 'ValidationHandler.class.php';

/**
 * ConcreteHandlerクラスに相当する
 */
class AlphabetValidationHandler extends ValidationHandler
{

    /**
     * 自クラスが担当する処理を実行
     */
    protected function execValidation($input)
    {
        return preg_match('/^[a-z]*$/i', $input);
    }

    /**
     * 処理失敗時のメッセージを取得する
     */
    protected function getErrorMessage()
    {
        return '半角英字で入力してください';
    }
}
NumberValidationHandlerクラス(NumberValidationHandler.class.php
<?php
require_once 'ValidationHandler.class.php';

/**
 * ConcreteHandlerクラスに相当する
 */
class NumberValidationHandler extends ValidationHandler
{

    /**
     * 自クラスが担当する処理を実行
     */
    protected function execValidation($input)
    {
        return (preg_match('/^[0-9]*$/', $input) > 0);
    }

    /**
     * 処理失敗時のメッセージを取得する
     */
    protected function getErrorMessage()
    {
        return '半角数字で入力してください';
    }
}

 NotNullValidationHandlerクラスは、入力された文字列が空文字でないかどうかを検証します。空文字の場合、検証失敗となります。

NotNullValidationHandlerクラス(NotNullValidationHandler.class.php
<?php
require_once 'ValidationHandler.class.php';

/**
 * ConcreteHandlerクラスに相当する
 */
class NotNullValidationHandler extends ValidationHandler
{

    /**
     * 自クラスが担当する処理を実行
     */
    protected function execValidation($input)
    {
        return (is_string($input) && $input !== '');
    }

    /**
     * 処理失敗時のメッセージを取得する
     */
    protected function getErrorMessage()
    {
        return '入力されていません';
    }
}

 ValidationHandlerクラスのサブクラスの最後はMaxLengthValidationHandlerクラスです。このクラスは、入力された文字列の長さが指定された長さ以下かどうかを検証します。この長さの指定は、コンストラクタでおこなっています。

MaxLengthValidationHandlerクラス(MaxLengthValidationHandler.class.php
<?php
require_once 'ValidationHandler.class.php';

/**
 * ConcreteHandlerクラスに相当する
 */
class MaxLengthValidationHandler extends ValidationHandler
{

    private $max_length;

    public function __construct($max_length = 10)
    {
        parent::__construct();
        if (preg_match('/^[0-9]{,2}$/', $max_length)) {
            throw new RuntimeException('max length is invalid (0-99) !');
        }
        $this->max_length = (int)$max_length;
    }

    /**
     * 自クラスが担当する処理を実行
     */
    protected function execValidation($input)
    {
        return (strlen($input) <= $this->max_length);
    }

    /**
     * 処理失敗時のメッセージを取得する
     */
    protected function getErrorMessage()
    {
        return $this->max_length . 'バイト以内で入力してください';
    }
}

 そして、検証のクラス群を利用するクライアント側のコードです。動作を簡単に確認できるよう、入力用のHTMLフォームも表示します。

 また、検証を実行するコードがValidationHandler型オブジェクトのvalidateメソッドを呼び出すだけになっていることを確認してください。if文で実装する場合と比べて、非常に簡単なコードになっていますね。

クライアント側コード(chain_of_responsibility_client.php
<?php
require_once 'MaxLengthValidationHandler.class.php';
require_once 'NotNullValidationHandler.class.php';

if (isset($_POST['validate_type']) && isset($_POST['input'])) {
    $validate_type = $_POST['validate_type'];
    $input = $_POST['input'];

    /**
     * チェーンの作成
     * validate_typeの値によってチェーンを動的に変更
     */
    $not_null_handler = new NotNullValidationHandler();
    $length_handler = new MaxLengthValidationHandler(8);

    $option_handler = null;
    switch ($validate_type) {
        case 1:
            include_once 'AlphabetValidationHandler.class.php';
            $option_handler = new AlphabetValidationHandler();
            break;
        case 2:
            include_once 'NumberValidationHandler.class.php';
            $option_handler = new NumberValidationHandler();
            break;
    }

    if (!is_null($option_handler)) {
        $length_handler->setHandler($option_handler);
    }
    $handler = $not_null_handler->setHandler($length_handler);

    /**
     * 処理実行と結果メッセージの表示
     */
    $result = $handler->validate($_POST['input']);
    if ($result === false) {
        echo '検証できませんでした';
    } elseif (is_string($result) && $result !== '') {
        echo '<p style="color: #dd0000;">' . $result . '</p>';
    } else {
        echo '<p style="color: #008800;">OK</p>';
    }
}
?>
<form action="" method="post">
  <div>
    値:<input type="text" name="input">
  </div>
  <div>
    検証内容:<select name="validate_type">
    <option value="0">任意</option>
    <option value="1">半角英字で入力されているか</option>
    <option value="2">半角数字で入力されているか</option>
    </select>
  </div>
  <div>
    <input type="submit">
  </div>
</form>

 入力フォームのプルダウンで検証する内容を選択できるようになっていますが、これによってAlphabetValidationHandlerオブジェクトもしくはNumberValidationHandlerオブジェクトが生成され、検証オブジェクトの鎖に追加されます。

 お分かりのように、検証のための処理を動的に追加しています。処理チェーンを動的に変更することができるのは、Chain of Responsibilityパターンの大きな特徴です。また、新しい検証クラスを作成し追加する場合も容易に対応できることが分かると思います。

 最後に、Chain of Responsibilityパターンを適用したサンプルアプリケーションのクラス図を示します。

f:id:shimooka:20141208185358p:image

適用例の実行結果

 Chain of Responsibilityパターンを適用したサンプルの実行結果ですが、文字列の検証に成功した場合は次のようになります。

f:id:shimooka:20141208185400p:image

一方、文字列の検証に失敗した場合は次のとおりです。

f:id:shimooka:20141208185359p:image

Chain of Responsibilityパターンのオブジェクト指向的要素

 Chain of Responsibilityパターンは「ポリモーフィズム」を活用したパターンです。

 Chain of Responsibilityパターンの特徴は、処理オブジェクトのチェーンです。つまり、Handler型のオブジェクトのチェーンです。このチェーンは、Handlerクラスの内部に保持されたHandler型のオブジェクトです。実際には、HandlerクラスのサブクラスであるConcreteHandlerクラスのインスタンスですが、Handlerクラス自身からはこのオブジェクトが具体的にどのクラスなのかは意識していません。ただ、Handler型のオブジェクトであるというだけです。

 つまり、内部に保持されたオブジェクトは、具体的にどのようなクラスであれ、Handler型のオブジェクト、言い換えると、Handlerクラスのサブクラスインスタンスであれば、問題なく動作するということになります。

 この結果、チェーンの組み替えや、新しいConcreteHandlerクラスを追加したりできるのです。

関連するパターン

 CompositeパターンはChain of Responsibilityパターンと併用される場合があります。

まとめ

 ここでは、オブジェクトを鎖のように繋いで問題を対処するChain of Responsibilityパターンを見てきました。

[][]PHPによるデザインパターン入門 - Builder〜生成の手順と手段を分離する

このエントリは、Do You PHP?(www.doyouphp.jp)で公開していたコンテンツを移行/加筆/修正したものです。公開の経緯はこちらをどうぞ。目次はこちらです。サンプルコードを手直ししたものgithubに上げてありますのでそちらもどうぞ。

GoF本における分類

 生成+オブジェクト

はじめに

 ここではBuilderパターンについて見ていきましょう。

 「builder」とは、「建築者」や「建設業者」の意味を持つ単語ですが、たとえば、家を建てることを考えてみましょう。最終的に建てられる家は、「どの様な順序で、どこに何を配置していくか」という「手順」と、「柱や壁、屋根に何を使うか」という「材料」によって大きく違ってきます。

 Builderパターンは、この「手順」と「材料」を分けておき、同じ手順で異なるオブジェクトを生成させるパターンです。

たとえば

 ある条件によって作成するオブジェクトを変更するといった場合を考えてみましょう。

 普通に考えると、if文やswitch文で処理を分岐し、生成するオブジェクトを切り替えるだけで済んでしまいそうですね。

 しかし、オブジェクトを生成するのに複雑な手順が必要だった場合はどうでしょうか?大量のif文やswitch文で処理を分岐してやる必要がありそうですね。

 こういった場合、「何を作るのか」に依存しないように具体的な生成手順をまとめておけると、簡単に色々なオブジェクトを生成できそうです。

 もうひとつ、あるフォーマットに従ったデータを読み込んで、そのデータを格納するクラスを考えてみましょう。この場合、データの解析処理はどこに記述すると良いでしょうか?

 たとえば、このクラスの中でデータを処理させるとしましょう。この場合、データを渡すだけでクラスのインスタンスが生成でき、またデータの処理もクラスの内部に閉じこめることができます。

 しかし、データの処理が非常に複雑な場合はどうでしょうか?このクラスのコードの大部分は、その処理に関するコードになってしまうでしょう。このクラスの本来の目的は、データを格納することのはずですが、データの解析処理も含めると非常に分かりにくいコードになってしまいます。

 データを保持するクラスにはできるだけその目的に専念させ、データの処理部分は他のクラスに任せたいものです。そうすればクラスの「責任」がはっきりし、クラスの構造がシンプルになりますので、コードも分かりやすいものになるはずです。また、クラスの再利用性も高まると予想されます。

 このように、オブジェクトを「何を生成するか」と「どのように生成するか」を分離するパターンがBuilderパターンです。

Builderパターンとは?

 Builderパターンは、オブジェクトの生成に注目したパターンで、オブジェクトの「生成手順」と「生成手段」を分離することを目的としています。

 GoF本では、Builderパターンの目的は次のように定義されています。

複合オブジェクトについて、その作成過程を表現形式に依存しないものにすることにより、同じ作成過程で異なる表現形式のオブジェクトを生成できるようにする

 Builderパターンでは、まずクライアントがどのようなオブジェクトを生成するかを選択します。つまり「材料」を選択するということです。この材料は、最終的に生成されるオブジェクトの生成処理を知っています。この材料を「建築者」に渡すことで、実際のオブジェクトの生成をお願いします。

 一方の「建築者」ですが、渡された材料からどの様なものができるのか知りません。ただ、自らが知っている手順に沿って、オブジェクトの生成をおこなうだけです。

 1つのクラスに生成手順と生成手段をすべてまとめた場合、クラスが複雑になりすぎる傾向があります。ここでBuilderパターンを適用して生成手順を分離することで、構造がシンプルになり、再利用性も高まります。

Builderパターンの構造

 Builderパターンのクラス図と構成要素は、次のとおりです。

f:id:shimooka:20141208185055p:image

 オブジェクトの「生成手段」を提供するクラス群で最上位に位置するクラスです。オブジェクトを生成するためのAPIを定義します。

  • ConcreteBuilderクラス

 Builderクラスで提供されるAPIを実装するサブクラスです。また、生成したオブジェクトを取得するためのメソッドを提供します。

  • Directorクラス

 「建築者」に相当するクラスで、Builderクラスで定義されたAPIを使ってオブジェクトを生成します。

  • Productクラス

 最終的に生成されるオブジェクトのクラスです。

Builderパターンのメリット

 Builderパターンのメリットとしては、以下のものが挙げられます。

 Builderクラスでは、Directorクラスにオブジェクトを生成するためのAPIを提供しています。Directorクラスでは、このAPIを使ってのみProductオブジェクトを生成します。つまり、DirectorクラスはProductオブジェクトの生成過程やProductオブジェクトの生成手段を知りません。このため、新しいProductオブジェクトを作る必要がある場合、新しいConcreteBuilderクラスを追加するだけで済みます。

 Builderパターンは、オブジェクトの生成過程と生成手段を分離するパターンです。Directorクラスにはオブジェクト生成過程のコードだけが、ConcreteBuilderクラスにはオブジェクトの生成手段のコードだけが記述されることになります。つまり、生成過程と生成手段それぞれに関するプログラムコードを凝縮できるということです。この結果、生成過程、生成手段を独立して修正・拡張することが可能になります。

Builderパターンの適用

 Builderパターンの適用例を見てみましょう。

 これは、ニュース一覧をインターネット経由で取得し、一覧表示するアプリケーションです。

 まずは、1つの記事を格納するNewsクラスから始めましょう。Newsクラスは、記事のタイトル、URL、記事の対象日付を保持するだけのクラスで、これら3要素をコンストラクタで受け取ります。特に問題はありませんね。

Newsクラス(News.class.php
<?php
class News
{
    private $title;
    private $url;
    private $target_date;

    public function __construct($title, $url, $target_date)
    {
        $this->title = $title;
        $this->url = $url;
        $this->target_date = $target_date;
    }

    public function getTitle()
    {
        return $this->title;
    }

    public function getUrl()
    {
        return $this->url;
    }

    public function getDate()
    {
        return $this->target_date;
    }
}

 続けて、外部のサイトからニュース記事を取得し、Newsオブジェクト配列を作成するクラスたちです。

 まずは、最終的なNewsオブジェクト配列を生成するNewsDirectorクラスです。Directorクラスに相当します。コンストラクタ引数に、NewsBuilder型のオブジェクトが指定できるようになっています。また、実際のNewsオブジェクトの生成は、内部に保持したNewsBuilderオブジェクトを使って生成しています。

NewsDirectorクラス(NewsDirector.class.php
<?php
require_once 'NewsBuilder.class.php';

/**
 * Directorクラスに相当する
 */
class NewsDirector
{
    private $builder;
    private $url;

    public function __construct(NewsBuilder $builder, $url)
    {
        $this->builder = $builder;
        $this->url = $url;
    }

    public function getNews()
    {
        $news_list = $this->builder->parse($this->url);
        return $news_list;
    }
}

 次に、先ほど出てきたNewsBuilderクラスについて説明しましょう。

 これはインターフェースとして宣言しており、parseメソッドを定義しています。先のNewsDirectorクラスは、このparseメソッドを使ってNewsオブジェクト配列を取得しています。

NewsBuilderクラス(NewsBuilder.class.php
<?php
/**
 * Builderクラスに相当する
 */
interface NewsBuilder {
    public function parse($data);
}

 先ほどのNewsBuilderクラスを実装するクラスが、RssNewsBuilderクラスです。名前からも分かるように、ニュース記事としてRSSからデータを取得します。

 RSSRDF Site Summary)は、ウェブログや新聞社、その他企業でも更新情報の配信などに利用されている文章フォーマットです。

 今回は、PHP5から導入されたSimpleXML拡張機能を利用しています。

RssNewsBuilderクラス(RssNewsBuilder.class.php
<?php
require_once 'News.class.php';
require_once 'NewsBuilder.class.php';

/**
 * ConcreteBuilderクラスに相当する
 */
class RssNewsBuilder implements NewsBuilder
{
    public function parse($url)
    {
        $data = simplexml_load_file($url);
        if ($data === false) {
            throw new Exception('read data [' .
                                htmlspecialchars($url, ENT_QUOTES, mb_internal_encoding())
                                . '] failed !');
        }

        $list = array();
        foreach ($data->item as $item) {
            $dc = $item->children('http://purl.org/dc/elements/1.1/');
            $list[] = new News($item->title,
                               $item->link,
                               $dc->date);
        }
        return $list;
    }
}

 最後にクライアント側のコードです。NewsDirectorクラスのコンストラクタに、RssNewsBuilderオブジェクトURLを渡していますね。たとえば、他のフォーマットで記述された記事データからNewsオブジェクト配列を生成する場合、このRssNewsBuilderオブジェクトを変更するだけで対応可能になります。これは、「何を生成するか」と「どのように生成するか」を切り分けた結果、可能になるものです。

クライアント側コード(builder_client.php
<?php
require_once 'NewsDirector.class.php';
require_once 'RssNewsBuilder.class.php';

$builder = new RssNewsBuilder();
$url = 'http://www.php.net/news.rss';

$director = new NewsDirector($builder, $url);
foreach ($director->getNews() as $article) {
    printf('<li>[%s] <a href="%s">%s</a></li>',
           $article->getDate(),
           $article->getUrl(),
           htmlspecialchars($article->getTitle(), ENT_QUOTES, mb_internal_encoding())
    );
}

 最後にサンプルコードのクラス図を示しておきます。

f:id:shimooka:20141208185053p:image

適用例の実行結果

 Builderパターンを適用したサンプルの実行結果は、次のようになります。

f:id:shimooka:20141208185054p:image

Builderパターンのオブジェクト指向的要素

 Builderパターンは「ポリモーフィズム」と「委譲」を活用しているパターンです。

 「建築者」であるDirectorクラスには、「材料」であるBuilder型のオブジェクト、具体的にはConcreteBuilderクラスのインスタンスが渡されます。ここで、渡されたBuilder型のオブジェクト具体的にどのクラスのインスタンスなのかを知りません。しかし、Builderクラスで提供されているメソッドは知っていますし、それらを呼び出すことで目的とするオブジェクトが生成されることも知っています。ただ、具体的にどの様なオブジェクトが生成されるかは知りません

 ここまで見てきたように、DirectorクラスはBuilderクラスのAPIしか知りません。それらAPIの具体的な処理内容は、BuilderクラスのサブクラスであるConcreteBuilderクラスで実装されています。つまり、ConcreteBuilderクラスがどのようなものであれ、BuilderクラスのAPIが実装されているものであれば、Directorクラスは正しく機能するということになります。

 この「具体的な実装が何かを知らない」ことが、オブジェクト指向プログラミングでは重要になってきます。また「知らないからこそ入れ替えが可能」になるのです。

 また、DirectorクラスとBuilderクラスの間には、強い結びつきはありません。Builder型のオブジェクトをDirectorクラスの内部に保持することで、ゆるく結びつけられています。Directorクラスの処理内容は、内部に保持したBuilder型のオブジェクトメソッドを呼び出し、具体的な処理を任せています。この関係を委譲と呼びます。この緩い結びつけのため、Builder型のオブジェクトプログラムの実行中に変更したりできるのです。

関連するパターン

 Abstract Factoryパターンも複雑なオブジェクトを生成するパターンです。Builderパターンは複雑なオブジェクトを段階的に生成していく手順に注目したパターンですが、Abstract Factoryパターンは、それぞれの部品の集まりに注目したパターンです。

 Builderパターンによって生成されるオブジェクトは、Compositeパターンになる場合があります。

まとめ

 ここでは、「何を生成するか」と「どのように生成するか」を切り分けるBuilderパターンを見てきました。

[][]PHPによるデザインパターン入門 - Bridge〜実装と機能の架け橋

このエントリは、Do You PHP?(www.doyouphp.jp)で公開していたコンテンツを移行/加筆/修正したものです。公開の経緯はこちらをどうぞ。目次はこちらです。サンプルコードを手直ししたものgithubに上げてありますのでそちらもどうぞ。

GoF本における分類

 構造+オブジェクト

はじめに

 ここではBridgeパターンについて説明します。

 「bridge」とは「橋」の意味ですね。橋は川の両岸や島と島など、ある点とある点を結ぶ役割を持っています。

 では、Bridgeパターンは、何と何を結ぶ橋なのでしょうか?早速、見てみましょう。

たとえば

 何らかの処理を実装する場面を考えてみましょう。当然ですが、「何をするのか」は決まっていますね?しかし、「どうやって実現するのか」が色々考えられる場合があります。

 たとえば、データのソート処理はその代表例と言えるでしょう。「ソートをする」という「何をするのか」は分かっていても、「どうやって実装するのか」には、バブルソートヒープソートクイックソートなど様々なロジックが存在します。

 このような場合、それぞれの実装ごとにクラスを用意してやることが最も単純になるでしょう。しかし、クラスの数があっという間に増えてしまいます。

 また、機能を拡張しようとした場合、すべてのクラスを変更する必要がでてきてしまいます。クラスの数が多ければ多いほど、修正に要する作業は大きくなってしまいます。

 もうひとつ、何らかのデータソースからデータを読み込んで一覧表示するアプリケーションを考えてみましょう。このアプリケーションの機能、つまり「何をするのか」は、以下のようにまとめられると思います。

  • データソースを開く
  • データを読み込む
  • データを一覧表示する

 この程度のアプリケーションであれば、みなさんも簡単に作成してしまうことでしょう。

 ここで、データソースへのアクセス方法やデータの取得方法、データの表示形式が色々考えられる場合はどうでしょうか?if文やswitch文を使って処理を分岐させることになるでしょう。しかし、新しい機能の追加や実装の変更があるたび、コードの修正をおこなっていてはテストをやり直す必要がありますし、if文やswitch文も複雑になり、メンテナンスビリティの悪いコードになってしまいます。

 これは、「何をするのか」と「どうやって実現するのか」を一緒に考えてしまっているから起こっている問題です。ここで「何をするのか」と「どうやって実現するのか」を分けて考えてみると、機能を拡張する場合は「何をするのか」側を変えることになりますし、実装の方法を変える場合は「どうやって実現するのか」側を変えれば良いことになります。

 「何をするのか」と「どうやって実現するのか」分けて考え、これらを結びつけるための橋を用意するパターン、それがBridgeパターンです。

Bridgeパターンとは?

 Bridgeパターンはオブジェクトの構造に注目したパターンで、「機能を提供するクラス群」と「実装を提供するクラス群」を分けることを目的としています。

 GoF本では、Bridgeパターンの目的は次のように定義されています。

抽出されたクラスと実装を分離して、それらを独立に変更できるようにする。

 「分ける」と言っても「インターフェース」と「実装クラス」を分けることを言っているのではなく、「何をするのか」と「どうやって実現するのか」を分けるということです。また、「Bridge」とは「橋」の意味ですが、委譲を使うことで「機能を提供するクラス群」と「実装を提供するクラス群」を橋渡ししているように見えることから名付けられています。

Bridgeパターンの構造

 Bridgeパターンのクラス図と構成要素は、次のようになります。

f:id:shimooka:20141208184951p:image

  • Abstractionクラス

 「何をするのか」を実現するクラス群で最上位に位置するクラスです。内部には、Implementorオブジェクトを保持していて、Implementorクラスが提供する機能をclientに提供します。

  • RefinedAbstractionクラス

 Abstractionクラスで提供される機能を拡張するサブクラスです。

  • Implementorクラス

 「どうやってするのか」を実現するクラス群で最上位に位置するクラスです。クラスではなく、インターフェースとして実装される場合もあります。また、Abstractionクラスで提供しているAPIに一致する必要はありません。

  • ConcreteImplementorAクラス、ConcreteImplementorBクラス

 Implementorクラスを継承したサブクラスです。このクラスに具体的な実装をおこないます。

Bridgeパターンのメリット

 Bridgeパターンのメリットとしては、以下のものが挙げられます。

  • クラス階層の見通しが良くなる

 「機能」と「実装」を提供するクラス群が分けられているので、クラス階層を理解しやすく、見通しが良くなります。つまり、保守性が高くなると言えます。機能と実装が1つのクラスで実装されていると、概してクラス階層が複雑になり、どの部分が機能なのか実装なのかが分かりづらくなるため、保守性が低くなりがちです。

  • 最終的に作成すべきクラス数を抑えることができる

 継承を使った単純な多態性を使った場合と比べ最終的に作成すべきクラス数を抑えることができます。例として、機能の種類が4つ、実装の種類が3つある場合を考えてみます。継承を使った単純な多態性のみの場合ですと、

親クラス:1 + サブクラス:12(4×3)= 13

のクラスを作成する必要があります。一方、Bridgeパターンを使うと、

親クラス:2 + サブクラス:7(4+3)= 9

のクラスを作成することになります。当然、機能機能や実装が増えるたびに修正しなければならないクラスの数に開きが出てくることは明らかです。

  • 機能の拡張と実装の切り替えが容易

 「機能」と「実装」を分けることで、お互いに影響することなく拡張や切り替えが可能になります。機能を拡張したい場合、機能を提供するクラス側のみ変更することになります。また、実装を追加したい場合も同様のことが言えます。

Bridgeパターンの適用

 Bridgeパターンの適用例を見てみましょう。

 ここでは、データを取得して表示するアプリケーションにBridgeパターンを適用してみます。このアプリケーションはかなり単純ですのでBridgeパターンを適用するほどではありませんが、Bridgeパターンを適用すると設計がスマートになります。

 まずは「どうやって実現するのか」側から見ていきましょう。

 DataSourceインターフェースは「どうやって実現するのか」側の最上位に位置するクラスで、Implementorクラスに相当します。今回はインターフェースとして実装しています。また、3つのメソッドopen、read、closeが定義されていますが、これは後ほど出てくるListingクラスが利用するAPIになります。

DataSourceインターフェース(DataSource.class.php
<?php
/**
 * Implementorに相当する
 * このサンプルでは、インターフェースとして実装
 */
interface DataSource {
    public function open();
    public function read();
    public function close();
}

 続けてConcreteImplementorクラスに相当するFileDataSourceクラスです。DataSourceインターフェースを実装し、定義された3メソッドを具体的に実装しています。今回は名前の通り、データソースとしてファイルを使用しています。このファイル名はコンストラクタ引数として指定するようになっています。

FileDataSourceクラス(FileDataSource.class.php
<?php
require_once 'DataSource.class.php';

/**
 * Implementorクラスで定義されている機能を実装する
 * ConcreteImplementorに相当する
 */
class FileDataSource implements DataSource
{

    /**
     * ソース名
     */
    private $source_name;

    /**
     * ファイルハンドラ
     */
    private $handler;

    /**
     * コンストラクタ
     * @param $source_name ファイル名
     */
    function __construct($source_name) {
        $this->source_name = $source_name;
    }

    /**
     * データソースを開く
     * @throws Exception
     */
    function open() {
        if (!is_readable($this->source_name)) {
            throw new Exception('データソースが見つかりません');
        }
        $this->handler = fopen($this->source_name, 'r');
        if (!$this->handler) {
            throw new Exception('データソースのオープンに失敗しました');
        }
    }

    /**
     * データソースからデータを取得する
     * @return string データ文字列
     */
    function read() {
        $buffer = array();
        while (!feof($this->handler)) {
            $buffer[] = fgets($this->handler);
        }
        return join($buffer);
    }

    /**
     * データソースを閉じる
     */
    function close() {
        if (!is_null($this->handler)) {
            fclose($this->handler);
        }
    }
}

 さて、もう一方の「何をするのか」側も見てみましょう。

 Listingクラスは「何をするのか」側の最上位に位置するクラスで、利用者に提供するAPIを定義しています。このAPIは、ImplementorクラスであるDataSourceインターフェースと同じAPIとしています。また、内部にDataSource型のオブジェクトを保持するようになっており、open、read、closeの各メソッドは具体的な処理をこのオブジェクトに委譲しています。

 この部分が「何をするのか」と「どうやって実現するのか」を結ぶ「橋」となっています。お分かりでしょうか?

 また、このクラスに実装側の具体的なクラス名が出てきていないことを確認してください。つまり、具体的な実装を意識することなく、利用者側に機能のAPIを提供することが可能になっていることが分かります。

Listingクラス(Listing.class.php
<?php
require_once 'DataSource.class.php';

class Listing
{
    private $data_source;

    /**
     * コンストラクタ
     * @param $source_name ファイル名
     */
    function __construct($data_source) {
        $this->data_source = $data_source;
    }

    /**
     * データソースを開く
     */
    function open() {
        $this->data_source->open();
    }

    /**
     * データソースからデータを取得する
     * @return array データの配列
     */
    function read() {
        return $this->data_source->read();
    }

    /**
     * データソースを閉じる
     */
    function close() {
        $this->data_source->close();
    }
}

 次は、Listingクラスの機能を拡張したクラスです。ExtendedListingクラスはListingクラスを継承し、さらに新しい機能のためのメソッドreadWithEncodeが追加されています。

 このクラスにも実装側の具体的なクラス名は出てきていませんね。具体的な実装と関係なく、機能が拡張できています。

ExtendedListingクラス(ExtendedListing.class.php
<?php
require_once 'Listing.class.php';

/**
 * Listingクラスで提供されている機能を拡張する
 * RefinedAbstractionに相当する
 */
class ExtendedListing extends Listing
{

    /**
     * コンストラクタ
     * @param $source_name ファイル名
     */
    function __construct($data_source) {
        parent::__construct($data_source);
    }

    /**
     * データを読み込む際、データ中の特殊文字を変換する
     * @return 変換されたデータ
     */
    function readWithEncode() {
        return htmlspecialchars($this->read(), ENT_QUOTES, mb_internal_encoding());
    }

}

 クライアント側のコードも見てみましょう。ここでは、ListingクラスとExtendedListingクラスの両方をインスタンス化しています。コンストラクタ引数に、データの読み込み処理を具体的に実装したクラスFileDataSourceを指定しています。

クライアント側コード(bridge_client.php
<?php
require_once 'Listing.class.php';
require_once 'ExtendedListing.class.php';
require_once 'FileDataSource.class.php';

/**
 * Listingクラス、ExtendedListingクラスをインスタンス化する。
 * 具体的な処理クラスとして、FileDataSourceクラスを使う。
 * データファイルは、data.txt
 */
$list1 = new Listing(new FileDataSource('data.txt'));
$list2 = new ExtendedListing(new FileDataSource('data.txt'));

try {
    $list1->open();
    $list2->open();
}
catch (Exception $e) {
    die($e->getMessage());
}

/**
 * 取得したデータの表示(readメソッド)
 */
$data = $list1->read();
echo $data;

/**
 * 取得したデータの表示(readWithEncodeメソッド)
 */
$data = $list2->readWithEncode();
echo $data;

$list1->close();
$list2->close();

 今回サンプルとして用意したデータファイルは次のようなものです。

データファイル(data.txt)
<h1>This is a sample of the Bridge Pattern !</h1>

 最後に、Bridgeパターンを適用したサンプルのクラス図を確認しておきましょう。

f:id:shimooka:20141208184949p:image

適用例の実行結果

 Bridgeパターンを適用したサンプルの実行結果は、次のようになります。

f:id:shimooka:20141208184950p:image

Bridgeパターンのオブジェクト指向的要素

 Bridgeパターンは「ポリモーフィズム」と「委譲」を非常に活用しているパターンです。

 機能を提供するクラス側では、まずクライアント側に提供する「機能のAPI」をクラスで定義し、そのクラスを継承することで機能の拡張を実現します。一方、実装を提供するクラス側でも同様に、機能側のクラスに提供する「実装のAPI」をインターフェースもしくはクラスで定義し、それらを実装もしくは継承することで異なる実装を実現しています。ここまでの内容を見てみると、機能を提供するクラス群と実装を提供するクラス群はそれぞれTemplate Methodパターンになる場合があります。お分かりでしょうか?

 では、ここからが本番です。クライアントに提供する具体的な処理の実装を、実装を提供するクラス群に委譲している、つまり、実装を提供するクラス群の最上層で定義されている「実装のAPI」に基づいたクラスに任せています。ここがBridgeパターンの神髄ともいうべきところで、「Bridge」と名付けられた所以です。この委譲を使うことにより、実装側のクラスにある具体的な処理内容を意識することがなくなります。この「意識することがなくなる」ため、処理を切り替える場合も機能側に属するクラスを一切変更する必要がなくなります。また、あとで新しい実装を追加したりする事が可能になります。

 また、Bridgeパターンでは、クライアント・機能側のクラスとの間にある「機能のAPI」と、機能側のクラス・実装側のクラスとの間にある「実装のAPI」で、ポリモーフィズムを二度使っている、という見方もできますね。

関連するパターン

 ConcreteImplementorを適切に構築するために使われる場合があります。

  • Adapterパターン

 Adapterパターンの構造と比較するとよく分かりますが、BridgeパターンはAdapterパターンと非常によく似ています。また、本質的にも変わりありません。

 ただし、Adapterパターンのように「既存クラスを再利用するために繋ぎ合わせる」といった後天的な理由ではなく、「設計の段階で実装と機能を分離し、それぞれを繋ぎ合わせる」といった先天的な理由で導入されます。その場合、実装の変更が考えられる処理については、実装のクラス階層に処理を委譲するようにします。

まとめ

 ここでは「機能」のクラス階層と「実装」のクラス階層を橋のように結ぶBridgeパターンについて見てきました。