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

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パターンについて見てきました。

2014-12-15

[][]PHPによるデザインパターン入門 - Abstract Factory〜関連する部品をまとめて作る工場

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

GoF本における分類

 生成+オブジェクト

はじめに

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

 「abstract factory」を直訳すると「抽象的な工場」となりますね。抽象的な工場…これは一体何なのでしょうか?

 GoFパターンの1つにFactory Methodパターンがあります。Factory Methodパターンは製品であるオブジェクトを作る「工場」を用意するパターンです。ここで見ていくAbstract Factoryパターンも同様にオブジェクトを生成するパターンの1つで、関連し合うオブジェクトの集まり、つまり部品の集まりを生成します。

 ここで、「抽象的な」というところがポイントです。この工場は「抽象的」な工場で、生成される部品も「抽象的」な部品になります。

 オブジェクト指向プログラミングでは、「物を抽象化する」ということが重要なポイントとなります。つまり、具体的な物を直接扱うのではなく、「具体的な物を抽象化した物」を扱うということです。

 Abstract Factoryパターンは、具体的なクラスを明確にすることなく、関連し合うオブジェクトの集まりを生成するパターンです。

たとえば

 データベースを使ったアプリケーションを構築する場合を考えてみましょう。

 近年の開発・設計方法論では、データベースに関する処理内容とそれ以外のアプリケーション固有の処理(ビジネスロジック)とを分けて設計・実装することが主流になっています。なぜなら、接続やデータの取得、トランザクションなどデータベースだけに関する処理は、本来ビジネスロジックとは関係がないためです。

 この手法DAO(Data Access Object)パターンと呼ばれます。DAOパターンを適用する場合、通常エンティティ(データベースの表)の単位でクラスを作成します。

 さて、データベースを使ったアプリケーションには、当然ですが何らかのデータベースが必要になります。しかし、プログラムを作成できても肝心のデータベースがないと動作しない、ひいてはテストできないといった状況になりがちです。しかし、場合によってはデータベースが用意できないまま先行して開発を行わなければならないこともあります。このような場合、データベースアクセスをすると見せかける擬似的なオブジェクト(モックオブジェクト)を使うと非常に有効です。このオブジェクトは「アクセスしているように見せかける」だけで、実際にはデータベースにアクセスしません。つまり、データベースがなくても、ビジネスロジック側を作成したりテストできます。

 しかし、モックオブジェクトを使って開発した場合でも、最終的には実際にデータベースにアクセスするクラスに切り替える必要があります。

 これはかなり大変な作業になります。しかし、ここでビジネスロジックを記述したコードを書き換えてしまっては再度テストをおこなわなければなりません。これでは何のためにモックオブジェクトを用意したのか分からなくなってしまいます。

 データベースアクセスクラスの集まりを、整合性を保ったまま簡単に切り替える…このような場面でAbstract Factoryパターンが活躍します。

Abstract Factoryパターンとは?

 Abstract Factoryパターンはオブジェクトの生成に注目したパターンで、関連するオブジェクトをその具体的なクラスを意識させないで生成することを目的としています。

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

互いに関連したり依存し合うオブジェクト群を、その具象クラスを明確にせずに生成するためのインターフェースを提供する。

 Abstract Factoryパターンでは、部品の役割を持つクラスとその部品を作る工場の役割を持つクラスが存在します。

 ただし、その工場には関連し合う部品を生成するためのメソッドがそれぞれ用意されます。また、関連し合う部品群の種類に応じて、その工場自身も「工場を生成するための工場」によって生成されます。

 これにより、状況に応じて生成される具体的な部品群を切り替えることができます。

 まとめると、Abstract Factoryパターンでは工場から生成される部品は抽象化されており、部品の具体的な内容や生成手順をクライアントが意識しなくて済むようになります。

Abstract Factoryパターンの構造

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

f:id:shimooka:20141208184646p:image

  • AbstractFactoryクラス

 部品であるAbstractProductクラスを生成するためのAPIを定義するクラスです。生成するのがConcreteProductクラスではなくAbstractProductクラスとして定義されているところがポイントです。

  • ConcreteFactoryクラス

 AbstractFactoryクラスのサブクラスで、定義された生成メソッドを実装するクラスです。ここには、具体的な部品であるConcreteProductクラスを返すよう実装されます。

  • AbstractProductクラス

 部品ごとのAPIを定義するクラスです。

  • ConcreteProductクラス

 AbstractProductクラスのサブクラスで、ConcreteFactoryクラスから返される具体的な部品クラスです。

 AbstractFactoryクラスとAbstractProductクラスのAPIのみを利用してプログラミングをおこないます。

Abstract Factoryパターンのメリット

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

 クライアントは具体的な工場や部品に直接アクセスするのではなく、抽象化された工場や部品のAPIのみを使って部品オブジェクトを生成したりアクセスしたりできます。このため、クライアントを変更することなく、具体的な工場や部品を変更することが可能です。

  • 利用する部品群の整合性を保つ

 関連し合うオブジェクトを扱う場合、その整合性を保つことが重要になります。Abstract Factoryパターンを適用することで、オブジェクト群の整合性を容易に保つことができます。

  • 部品群の単位で切り替えができる

 ConcreteFactoryクラスは関連する部品の集まりを生成します。つまり、ConcreteFactoryクラスを切り替えることで、関連する部品の集まりを容易に切り替えることができます。

Abstract Factoryパターンの適用

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

 ここでは、商品情報と注文情報を取得するアプリケーションを示しています。Abstract Factoryパターンを適用し、実データを扱う部品群とダミーデータを扱う部品群を一度に切り替えられるようにしています。また、先ほどのDAOパターンを適用して、データの取得部分を隠蔽しています。

 まずは、AbstractFactoryクラスに相当するクラスから見ていきましょう。

 DaoFactoryインターフェースは、2つの部品を生成するためのAPIが定義されています。createItemDaoメソッドとcreateOrderDaoメソッドです。特に難しいところはありませんね。

DaoFactoryインターフェース(DaoFactory.class.php
<?php
interface DaoFactory {
    public function createItemDao();
    public function createOrderDao();
}

 次は、DaoFactoryインターフェースを実装したクラスです。それぞれのcreateItemDaoメソッドとcreateOrderDaoメソッドに注目してください。DbFactoryクラスは実データ、MockFactoryクラスはダミーデータをそれぞれ扱うDAOオブジェクトを生成していることが分かります。

DbFactoryクラス(DbFactory.class.php
<?php
require_once 'DaoFactory.class.php';
require_once 'DbItemDao.class.php';
require_once 'DbOrderDao.class.php';

class DbFactory implements DaoFactory
{
    public function createItemDao()
    {
        return new DbItemDao();
    }
    public function createOrderDao()
    {
        return new DbOrderDao($this->createItemDao());
    }
}
MockFactoryクラス(MockFactory.class.php
<?php
require_once 'DaoFactory.class.php';
require_once 'MockItemDao.class.php';
require_once 'MockOrderDao.class.php';

class MockFactory implements DaoFactory
{
    public function createItemDao()
    {
        return new MockItemDao();
    }
    public function createOrderDao()
    {
        return new MockOrderDao();
    }
}

 続いて、DAOに関連するクラス群を見ていきましょう。

 ItemDaoインターフェースは商品情報、OrderDaoインターフェースは注文情報をそれぞれ取得するためのAPIを定義しています。これらのインターフェースは、AbstractProductクラスに相当します。

 このサンプルアプリケーションでは、findByIdメソッドにIDを渡すことで情報を取り出せるようにしています。

ItemDaoクラス(ItemDao.class.php
<?php
interface ItemDao {
    public function findById($item_id);
}
OrderDaoクラス(OrderDao.class.php
<?php
interface OrderDao {
    public function findById($order_id);
}

 次はConcreteProductクラスに相当するクラスです。

 先のAbstractFactoryクラスとConcreteFactoryクラスの関係と同様に、実データを扱うクラスとダミーデータを扱うクラスに分かれています。

 実データを扱うクラスはDbItemDaoクラスとDbOrderDaoクラスです。このサンプルアプリケーションでは、商品情報と注文情報をテキストファイルに保存してあり、DbItemDaoクラスとDbOrderDaoクラスがインスタンス化されるときに読み込まれ、商品・注文の各オブジェクトが生成されるようになっています。

 データファイルと商品クラス、注文クラスについては、後ほど説明します。

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

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

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

        $this->items = array();
        while ($buffer = fgets($fp, 4096)) {
            $item_id = trim(substr($buffer, 0, 10));
            $item_name = trim(substr($buffer, 10));

            $item = new Item($item_id, $item_name);
            $this->items[$item->getId()] = $item;
        }

        fclose($fp);
    }

    public function findById($item_id)
    {
        if (array_key_exists($item_id, $this->items)) {
            return $this->items[$item_id];
        } else {
            return null;
        }
    }
}
DbOrderDaoクラス(DbOrderDao.class.php
<?php
require_once 'OrderDao.class.php';
require_once 'Order.class.php';

class DbOrderDao implements OrderDao
{
    private $orders;
    public function __construct(ItemDao $item_dao)
    {
        $fp = fopen('order_data.txt', 'r');

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

        $this->orders = array();
        while ($buffer = fgets($fp, 4096)) {
            $order_id = trim(substr($buffer, 0, 10));
            $item_ids = trim(substr($buffer, 10));

            $order = new Order($order_id);
            foreach (split(',', $item_ids) as $item_id) {
                $item = $item_dao->findById($item_id);
                if (!is_null($item)) {
                    $order->addItem($item);
                }
            }
            $this->orders[$order->getId()] = $order;
        }

        fclose($fp);
    }

    public function findById($order_id)
    {
        if (array_key_exists($order_id, $this->orders)) {
            return $this->orders[$order_id];
        } else {
            return null;
        }
    }
}

 一方のダミーデータを扱うクラスは、MockItemDaoクラスとMockOrderDaoクラスとなります。これらのクラスは、決まった商品データと注文データを返します。

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('99', 'ダミー商品');
        return $item;
    }
}
MockOrderDaoクラス(MockOrderDao.class.php
<?php
require_once 'OrderDao.class.php';
require_once 'Order.class.php';

class MockOrderDao implements OrderDao
{
    public function findById($order_id)
    {
        $order = new Order('999');
        $order->addItem(new Item('99', 'ダミー商品'));
        $order->addItem(new Item('99', 'ダミー商品'));
        $order->addItem(new Item('98', 'テスト商品'));

        return $order;
    }
}

 次に、商品クラスと注文クラスを説明します。これらのクラスは、純粋に商品の情報や注文の情報を内部に保持するだけの役割を担っています。

 商品を表す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;
    }
}

 注文を表すOrderクラスは、コンストラクタで注文IDを受け取り、注文情報に含まれる商品情報はaddItemメソッドを通じて追加します。また、注文IDや含まれている商品情報へアクセスするためのメソッドも用意されています。

Orderクラス(Order.class.php
<?php
class Order
{
    private $id;
    private $items;
    public function __construct($id)
    {
        $this->id = $id;
        $this->items = array();
    }
    public function addItem(Item $item)
    {
        $id = $item->getId();
        if (!array_key_exists($id, $this->items)) {
            $this->items[$id]['object'] = $item;
            $this->items[$id]['amount'] = 0;
        }
        $this->items[$id]['amount']++;
    }
    public function getItems()
    {
        return $this->items;
    }
    public function getId()
    {
        return $this->id;
    }
}

 ここで、実データについて見ておきましょう。

 商品情報と注文情報は、固定長データとして用意しました。フォーマットはデータファイル内の先頭行にもありますが、詳細は表を参照してください。

商品情報(item_data.txt)
商品ID    商品名
1         限定Tシャツ
2         ぬいぐるみ
3         クッキーセット
商品情報のファイルフォーマット
項目開始位置終了位置備考
商品ID110
商品名11-
注文情報(order_data.txt)
注文ID    商品ID
1         3
2         1,3
3         1,2,3
4         2
5         2,3
注文情報のファイルフォーマット
項目開始位置終了位置備考
注文ID110
商品ID11-複数の場合、商品IDをカンマ区切りで繋げる

 ようやく最後のクライアント側コードの説明です。

 注目していただきたいのが、このコードには具体的な部品クラス、つまりConcreteProductクラスに相当するクラスが一切出てきていないということです。

 唯一登場しているのがConcreteFactoryクラスに相当するDbFactoryクラスとMockFactoryクラスです。つまり、ConcreteFactoryクラスを切り替えるだけで、部品群を綺麗に切り替えることができています。

クライアント側コード(abstract_factory_client.php
<?php
if (isset($_POST['factory'])) {
    $factory = $_POST['factory'];

    switch ($factory) {
        case 1:
            include_once 'DbFactory.class.php';
            $factory = new DbFactory();
            break;
        case 2:
            include_once 'MockFactory.class.php';
            $factory = new MockFactory();
            break;
        default:
            throw new RuntimeException('invalid factory');
    }

    $item_id = 1;
    $item_dao = $factory->createItemDao();
    $item = $item_dao->findById($item_id);
    echo 'ID=' . $item_id . 'の商品は「' . $item->getName() . '」です<br>';

    $order_id = 3;
    $order_dao = $factory->createOrderDao();
    $order = $order_dao->findById($order_id);
    echo 'ID=' . $order_id . 'の注文情報は次の通りです。';
    echo '<ul>';
    foreach ($order->getItems() as $item) {
        echo '<li>' . $item['object']->getName();
    }
    echo '</ul>';
}
?>
<hr>
<form action="" method="post">
  <div>
    DaoFactoryの種類:
    <input type="radio" name="factory" value="1">DbFactory
    <input type="radio" name="factory" value="2">MockFactory
  </div>
  <div>
    <input type="submit">
  </div>
</form>

 最後にAbstract Factoryパターンを適用したアプリケーションのクラス図は次のようになっていますので確認してみてください。

f:id:shimooka:20141208184643p:image

適用例の実行結果

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

サンプルの実行結果(DbFactoryを選択)

f:id:shimooka:20141208184645p:image

サンプルの実行結果(MockFactoryを選択)

f:id:shimooka:20141208184644p:image

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

 Abstract Factoryパターンは「ポリモーフィズム」を活用したパターンです。

 具体的な部品クラスであるConcreteProductクラスは、それぞれの部品ごとに共通のAPIを持っています。これは親クラスであるAbstractProductクラスで定義されています。つまり、クライアント側はAbstractProductクラスのAPIだけを使ってプログラミングすることで、具体的なConcreteProductクラスに依存しないコードになります。

 このことは、部品工場であるAbstractProductクラスとConcreteFactoryクラスにも言えます。この2つのクラスには親子関係がありますが、親クラスであるAbstractFactoryクラスで提供されるAPIだけを使うことで、どのConcreteFactoryクラスなのかを意識する必要がなくなり、具体的なConcreteFactoryクラスに依存しないコードになります。

 また、工場から生成される部品はAbstractProductクラスとして生成されますので、ここでも、どのConcreteProductクラスなのかを意識する必要がありません

 このため、どのConcreteFactoryクラスを使うかを切り替えるだけで、芋づるのように生成されるConcreteProductクラス群を切り替えることができます。

 オブジェクト指向プログラミングでは、具体的なクラスではなくインターフェース抽象化されたクラスのAPIに対してプログラミングすることが重要になります

関連するパターン

 AbstractFactoryクラスはFactory Methodパターンを使って実装される場合が多いですが、Prototypeパターンを適用して実装することも可能です。

 ConcreteFactoryクラスにSingletonパターンが適用される場合が多くあります。

まとめ

 ここでは関連し合う部品群を生成するAbstract Factoryパターンについて見てきました。

[][]PHPによるデザインパターン入門 - Iterator〜順々にアクセスする

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

GoF本における分類

 振る舞い+オブジェクト

はじめに

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

 「iterate」は「繰り返す」「反復する」といった意味ですので、「iterator」は「反復するもの」となるでしょうか。

 名前から想像できるように、Iteratorパターンはオブジェクトに対する反復操作をおこなうための統一APIを提供するパターンです。

たとえば

 PHPで複数の値を含むリストを表現する場合、どの様にコードを記述していますか?おそらく、配列を使うことが多いのではないかと思います。

複数の値を含むリスト
<?php
$arr = array('PHP', 'デザインパターン', 'iterator');
$arr[] = 'GoFパターン';
$arr['version'] = 'PHP5';

 次に、このリストに含まれる値をすべて表示する場合はどうでしょうか?多くの場合、for文やforeach文を使うと思います。特にforeach文を使用すると、添字が文字列である連想配列も扱うことができますね。

リストに含まれる値をすべて表示するPHPスクリプト
<?php
// for文を使用した場合
for ($i = 0; $i < count($arr); $i++) {
    echo $arr[$i] . '<br>';
}

// foreach文を使用した場合
foreach ($arr as $value) {
    echo $value . '<br>';
}

 では、ディレクトリツリーを表すような不規則な多次元配列の場合はどうでしょうか?ある程度のプログラミング経験があれば、再帰を使えば何とかなりそうだと気づくかもしれませんね。

 このように、基本的にはfor文やforeach文を使用することで、リストの要素を取り出すことができます。

 ここまではどの様なリストを処理するのかが、あらかじめ分かっている状況でしたね。しかし、どの様なリストを処理するのか分からない場合はどうでしょうか?つまり、リストの内部構造が分からない状況です。ここで「すべての場合を想定してループを用意して…」と考えてしまうかもしれませんが、ちょっと現実的ではありませんね。

 また、要素の値を判断して取り出す順序を変えたい場合はどうでしょうか?たとえば、氏名と年齢、性別を持つユーザオブジェクトのリストを考えてみましょう。このリストから氏名順にユーザオブジェクトを取り出したい場合もあれば、年齢順の場合もあるでしょう。女性、男性のどちらかだけというのもあり得ますね。また、それらの逆順で取り出したい場合もあるかもしれません。

 このような色々な要求に対応する場合、要素を取り出すスクリプトコードを修正することになってしまいがちです。つまり、コードの再利用はできないということです。

 さて、こういった場合、どの様にすれば良いでしょうか?

 最初のコード例では、for文もforeach文もリストから1つずつ要素を取り出していました。ここに注目してみましょう。

 やりたいことは、リストから要素を取り出すことです。しかし、リストはどの様な構造なのか分からなかったり、取り出す方法が複数あるかもしれません。

 そこで、「リストから1つずつ要素を取り出す」役割を担うものを用意するとどうでしょうか?利用側は「次の要素をちょうだい」とお願いするだけで済むようになりそうです。

 一方の「リストから1つずつ要素を取り出す」役の方はどのようになりそうでしょうか?たとえば「次の要素をちょうだい」と依頼された場合は、リストの構造によって返す要素を決定し、その要素を返す処理をおこなえば良いことになります。つまり、利用者に、「どの様な構造を持つリストなのか」を意識させないようにできます。また、ある条件にマッチする値だけ取り出したい場合も、この「リストから1つずつ要素を取り出す」役にうまく納めることができそうです。

 このように、リストの内部構造を隠したまま、それぞれの要素にアクセスさせるためのパターンがIteratorパターンです。

Iteratorパターンとは?

 Iteratorパターンはオブジェクトの振る舞いに注目したパターンで、利用者にリストの内部構造を隠したまま、それぞれの要素にアクセスさせるためのAPIを提供することを目的としています。

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

集約オブジェクトが基にある内部表現を公開せずに、その要素に順にアクセスする方法を提供する。

 Iteratorパターンでは、リストのように複数のオブジェクトをまとめる集約オブジェクトを走査するためのAPIを提供します。これにより、利用側は集約オブジェクトの内部を意識することなく、要素にアクセスできます。その結果、異なる内部構造を持つリストの要素に同じAPIでアクセスできます

 また、走査処理の具体的な実装を変えることで、逆方向に走査させたり任意の要素に直接アクセスさせることもできます。

Iteratorパターンの構造

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

f:id:shimooka:20141208184235p:image

 要素にアクセスするためのAPIを提供します。

  • ConcreteIteratorクラス

 Iteratorクラスのサブクラスで、定義されたAPIを実装するクラスです。ここには、リストの内部構造に依存する走査処理が実装されます。

  • Aggregateクラス

 Iteratorオブジェクトを返すAPIを提供します。

  • ConcreteAggregateクラス

 Aggregateクラスのサブクラスで、リスト固有のIteratorオブジェクトを返します。

Iteratorパターンのメリット

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

 リストを走査する処理はすべてConcreteIteratorクラス内に閉じこめられます。このため、クライアントはリストの内部構造を意識することなく走査することができます。

  • リストに対する操作方法を複数用意できる

 リストとそれを走査する役割のConcreteIteratorクラスが分かれていますので、異なる実装のConcreteIteratorクラスを用意することで、走査方法を容易に変更できます。

Iteratorパターンの適用

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

 PHP5よりSPL(Standard PHP Library)拡張が追加されました。SPL拡張では標準的な関数やクラス、インターフェース、例外が定義されていますが、様々なイテレータも併せて用意されています。PHP5を使ってIteratorパターンを適用する場合、SPL拡張をそのまま、もしくは拡張して使用する場面が多くなるでしょう。

 ここで挙げたサンプルアプリケーションは社員一覧を表示するものですが、次のSPL標準クラス・インターフェースを使用しています。

サンプルで使用するSPL標準クラス・インターフェース
名称概要
ArrayObjectクラス配列オブジェクトとして扱うためのラッパークラス
ArrayIteratorクラス配列用のiteratorクラス。ConcreteIteratorクラスに相当する。
FilterIteratorクラスiterator用のフィルタクラス。また、抽象クラスとして定義されているため、利用するにはクラスの継承をおこないacceptメソッドを実装する必要がある
IteratorAggregateインターフェースAggregateクラスに相当する。iteratorを生成するためのメソッドgetIteratorが宣言されている。

 では、早速サンプルアプリケーションのコードを見ていきましょう。

 まずは社員を表すEmployeeクラスです。このクラスは、コンストラクタに氏名・年齢・職務名を受け取り、内部に保持するだけのクラスです。特に難しいところはないと思います。

Employeeクラス(Employee.class.php
<?php
class Employee
{
    private $name;
    private $age;
    private $job;
    public function __construct($name, $age, $job)
    {
        $this->name = $name;
        $this->age = $age;
        $this->job = $job;
    }
    public function getName()
    {
        return $this->name;
    }
    public function getAge()
    {
        return $this->age;
    }
    public function getJob()
    {
        return $this->job;
    }
}

 次は社員リストを表すEmployeesクラスです。SPL拡張のIteratorAggregateインターフェースを実装し、ConcreteAggregateクラスに相当します。

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

class Employees implements IteratorAggregate
{
    private $employees;
    public function __construct()
    {
        $this->employees = new ArrayObject();
    }
    public function add(Employee $employee)
    {
        $this->employees[] = $employee;
    }
    public function getIterator()
    {
        return $this->employees->getIterator();
    }
}

 Employeesクラスは内部に複数のEmployeeオブジェクトを保持しますが、配列ではなくArrayObjectオブジェクトで管理します。また、getIteratorメソッドを実装し、ArrayObjectクラスのgetIteratorメソッドを呼び出しています。このメソッドからは、ArrayIteratorオブジェクトが返されます。利用側は、このArrayIteratorオブジェクトを使って社員リストにアクセスします。

 続いて、SalesmanIteratorクラスを見てみましょう。このクラスはSPL拡張のFilterIteratorクラスを継承しています。

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

class SalesmanIterator extends FilterIterator
{
    public function __construct($iterator)
    {
        parent::__construct($iterator);
    }

    public function accept()
    {
        $employee = $this->getInnerIterator()->current();
        return ($employee->getJob() === 'SALESMAN');
    }
}

 FilterIteratorクラスは「オブジェクトを包み込むクラス」で、包み込まれるオブジェクトに付加的な機能を提供します。これは、FilterIteratorクラスの抽象メソッドであるacceptメソッドを実装することで実現します。

 SalesmanIteratorクラスでは、包み込むArrayIteratorオブジェクトに「動作を変更する」という機能を提供するために、acceptメソッドに取得する条件を実装しています。その結果、条件にマッチした値のみを取り出すことが可能です。今回は、職務名が「SALESMAN」であるオブジェクトだけを取り出すようになっています。

 また、FilterIteratorクラスを使わなくとも、独自のIteratorを実装しても実現可能です。この場合も走査対象のEmployeesクラスやEmployeeクラスには影響を与えません。

 最後に、説明してきたクラス群を利用するクライアント側のコードです。

 まず、社員リストを作成し、イテレータを取り出しています。先ほど説明したとおり、イテレータはArrayIteratorオブジェクトになります。その後、イテレータメソッドを利用して一覧を表示する場合と、foreach文を使って表示しています。

クライアント側コード(iterator_client.php
<?php
require_once 'Employee.class.php';
require_once 'Employees.class.php';
require_once 'SalesmanIterator.class.php';

function dumpWithForeach($iterator) {
    echo '<ul>';
    foreach ($iterator as $employee) {
        printf('<li>%s (%d, %s)</li>',
               $employee->getName(),
               $employee->getAge(),
               $employee->getJob());
    }
    echo '</ul>';
    echo '<hr>';
}

$employees = new Employees();
$employees->add(new Employee('SMITH', 32, 'CLERK'));
$employees->add(new Employee('ALLEN', 26, 'SALESMAN'));
$employees->add(new Employee('MARTIN', 50, 'SALESMAN'));
$employees->add(new Employee('CLARK', 45, 'MANAGER'));
$employees->add(new Employee('KING', 58, 'PRESIDENT'));

$iterator = $employees->getIterator();

/**
 * iteratorのメソッドを利用する
 */
echo '<ul>';
while ($iterator->valid()) {
    $employee = $iterator->current();
    printf('<li>%s (%d, %s)</li>',
           $employee->getName(),
           $employee->getAge(),
           $employee->getJob());

    $iterator->next();
}
echo '</ul>';
echo '<hr>';

/**
 * foreach文を利用する
 */
dumpWithForeach($iterator);

/**
 * 異なるiteratorで要素を取得する
 */
dumpWithForeach(new SalesmanIterator($iterator));

 ご覧の通り、イテレータメソッドでもforeach文でも同様の結果を得られます。つまり、foreach文が暗黙的なイテレータとなり得ます。このため、Iteratorパターンを適用した場合、イテレータメソッドではなくforeach文が使われることが多いでしょう。ただし、取り出す方法はConcreteIteratorクラスに依存しています。

 そこで最後に、異なるイテレータを使って営業職の社員だけを一覧表示しています。このとき、Employeesオブジェクトや表示するためのコードを修正する必要がありませんし、新しいFilterIteratorクラスのサブクラスを作成するだけで新しい取得方法を追加できるということを確認してください。

 最後にIteratorパターンを適用したアプリケーションのクラス図は次のようになっています。なお、色付きのインターフェースは、先の説明では出てきていないものになります。

f:id:shimooka:20141208184233p:image

適用例の実行結果

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

f:id:shimooka:20141208184234p:image

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

 Iteratorパターンは「非カプセル化」をおこなうパターンです。

 これまで見てきたように、Iteratorパターンでは集約オブジェクトを走査する「操作」を他のクラスに任せています。

 本来、オブジェクト指向では、関連するデータと操作を1つの塊(オブジェクト)として扱うことでカプセル化を行いますので、集約オブジェクト自身に操作するためのコードを用意するのが本来の姿かもしれません。しかし、無理にカプセル化することでオブジェクトが肥大化したり、複雑になり過ぎる場合があります。たとえば、様々な走査をサポートしたい場合を考えてみてください。

 また、集約オブジェクトを走査する場面はかなり多いものと思います。当然、多くのクラスに似たようなコードを持たせることになりますね。しかし、それぞれのクラスに走査のためのコードを記述するよりも、別のクラスに記述して再利用した方が有利になると考えられます。また、集約オブジェクトに影響を与えないで具体的な実装を変えることもできます。

 Iteratorパターンでは、集約オブジェクトに対する操作を別クラスとして切り出して、オブジェクトが不要に肥大化したり複雑になることを避けています。確かに、集約オブジェクトを操作する場合は切り出されたクラスと連携する必要がありますが、切り出されたクラスを切り替えることで様々な操作をサポートできるようになっています。

関連するパターン

 Compositeパターンは再帰的な構造を作るパターンです。このような構造に対してIteratorパターンが適用されることがあります。

 適切なConcreteIteratorクラスをインスタンス化するために、Factory Methodパターンが適用される場合があります。

まとめ

 ここでは集約オブジェクトから要素を取り出すためのAPIを提供するIteratorパターンについて見てきました。集約オブジェクトとは別のクラスに具体的に取り出す処理を任せることで、様々な操作をサポートできるようになります。

[][]PHPによるデザインパターン入門 - Façade〜シンプルな唯一の窓口

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

GoF本における分類

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

はじめに

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

 「façade」は「ファサード」と読みます。あまり聞き慣れない言葉ですが、フランス語で「正面窓口」という意味です。

 Façadeパターンは、複雑な関連を持つクラス群を簡単に利用するための「窓口」を用意するパターンです。

たとえば

 世の中には個人的にしか利用しない簡単なシステムから世界規模で大々的に利用されるシステムまで、大小さまざまなシステムがあります。

 最初はごく簡単な機能だけしかなかった小さなシステムも、度重なる機能拡張や修正で徐々にそのシステムは大きくなっていきます。システムの規模が大きくなればなるほどクラスの数は増えていき、それに従ってクラスどうしの関係も複雑になっていきます。

 クラスどうしの関係が複雑になってくると、それを利用する側に急激な負担が発生します。それはクラスの使い方です。ありがちな話として、「このクラスはあのクラスと一緒に使う必要がある」とか「このクラスを使う場合は、前もってこちらのクラスのメソッドを呼び出し、あらかじめ処理をしておかなければいけない」といったものです。つまり、あるクラスを利用するために複数の他のクラスを知っている必要がある、ということです。

 こういった状況では、利用する側にミスが発生しやすくなり、思わぬ不具合やシステム障害の温床になってしまいます。また、システムのセキュリティ面では、使い方が複雑になればなるほどセキュリティの確保がしにくい状況になります。

 もし、クラスどうしの複雑な関係を意識しなくても利用できるシンプルな「窓口」があればどうでしょうか?利用する側は非常に便利になり、ミスが少なくなりそうですね。またセキュリティの確保も、バラバラなクラスどうしのままよりは格段に改善されそうです。

 このような場面でFaçadeパターンが活躍します。

Façadeパターンとは?

 Façadeパターンはクラスやオブジェクトの構造に注目したパターンで、複雑に連携しあうクラスやオブジェクトを容易に扱うためのAPIを提供することを目的としています。

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

サブシステム内に存在する複数のインターフェースに1つの統一インターフェースを与える。Façadeパターンはサブシステムの利用を容易にするための高レベルインターフェースを定義する。

 Façadeパターンは、複雑に関連しあうクラス群を隠蔽するようなクラスを用意し、そのクラスに統一されたAPIを実装します。利用側はそのAPIを通じてクラス群を利用します。

 こう見てみると、みなさんもすでにどこかで使ったことがあるかもしれませんね。

 一般的に、システムがある規模より大きくなると、より小さなサブシステム単位に分割されます。このサブシステムをシンプルに利用できるようにするために、Façadeパターンが利用されます。Façadeパターンを適用すると、あるAPIだけを呼び出すだけでサブシステムを利用できるようになります。また、サブシステムどうしの依存関係もシンプルになります。

Façadeパターンの構造

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

f:id:shimooka:20141208183624p:image

  • Façadeクラス

 サブシステムで提供される統一APIを持つクラスです。サブシステム内のクラス同士の関係を知っています。また、クライアントからの要求を、サブシステム内の適切なオブジェクトに委譲します。

  • サブシステム内のクラス群

 サブシステムを構成するクラス群です。Façadeクラスの存在は知りません

 サブシステムを利用するクラスです。Façadeクラスを通じてサブシステムにアクセスします。

Façadeパターンのメリット

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

  • サブシステムの構成要素を隠蔽する

 Façadeパターンを適用すると、クライアントからはサブシステムの入り口しか見えなくなります。結果、クライアントが意識しなければならないクラスの数を抑えることができ、そのサブシステムを簡単に利用できます。

 Façadeクラスを通じてサブシステムにアクセスすることで、サブシステムとクライアントの結びつきがゆるくなります。つまり、クライアントとサブシステム内部との独立性がより高くなると言えます。独立性が高くなることで、クライアントのコードに影響を与えることなく、サブシステム内部を変更できます。

Façadeパターンの適用

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

 ここでは、注文処理をおこなうアプリケーションを取り上げます。この注文処理はサブシステムとして扱うことができるため、Façadeパターンを適用して注文処理を利用するためのシンプルなAPIを提供すると共に、クライアントとの結びつきをゆるくしています。

 まずは、商品クラスと注文商品クラス、注文クラスの3つから見ていきましょう。この3クラスはとてもシンプルなクラスです。

 商品を表すItemクラスは、商品ID、商品名、単価の各情報を保持するだけのシンプルなものです。コンストラクタで全ての情報を引数として受け取り、それぞれの情報にアクセスするためのメソッドを用意しています。

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

 注文する商品を表すOrderItemクラスは、内部に商品クラスのインスタンスとその注文数量を保持します。Itemクラスと同様、コンストラクタでItemオブジェクトと数量を受け取り、それらにアクセスするためのメソッドもあります。

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

class OrderItem
{
    private $item;
    private $amount;
    public function __construct(Item $item, $amount)
    {
        $this->item = $item;
        $this->amount = $amount;
    }
    public function getItem()
    {
        return $this->item;
    }
    public function getAmount()
    {
        return $this->amount;
    }
}

 OrderクラスはOrderItemオブジェクトを格納するクラスです。OrderItemを追加するためのaddItemがあります。

Orderクラス(Order.class.php
<?php
require_once 'OrderItem.class.php';

class Order
{
    private $items;
    public function __construct()
    {
        $this->items = array();
    }
    public function addItem(OrderItem $order_item)
    {
        $this->items[$order_item->getItem()->getId()] = $order_item;
    }
    public function getItems()
    {
        return $this->items;
    }
}

 では、ここからは商品の注文処理をおこなうクラス群を見ていきましょう。

 さて、商品の注文処理ではどの様な処理をおこなう必要があるでしょうか?

 商品在庫の確認や引当処理、決済や確認メールの送信など様々な処理が考えられますが、ここでは簡易的に商品在庫の引当と注文情報の処理の2つをおこないます。この2つの処理は、実際にはデータベースなどにアクセスしてそれぞれの情報を更新したり追加したりしますが、このアプリケーションではメッセージを表示するだけとなっています。

 これらの商品在庫の引当と注文情報の処理をおこなうクラスが、ItemDaoクラスとOrderDaoクラスです。これらのクラスには、データベースだけに関する処理をまとめるDAO(Data Access Object)パターン適用されています。

 ItemDaoクラスは、インスタンス化と同時に商品情報を初期化しています。また、Singletonパターンも適用されており、ItemDaoインスタンスを取得するためのgetInstanceメソッドが用意されています。また、商品IDからItemオブジェクトを取得するfindByIdメソッド、在庫の引当処理をおこなうsetAsideメソッドがあります。

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

class ItemDao
{
    private static $instance;
    private $items;
    private function __construct()
    {
        $fp = fopen('item_data.txt', 'r');

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

        $this->items = array();
        while ($buffer = fgets($fp, 4096)) {
            $item_id = trim(substr($buffer, 0, 10));
            $item_name = trim(substr($buffer, 10, 20));
            $item_price = trim(substr($buffer, 30));

            $item = new Item($item_id, $item_name, $item_price);
            $this->items[$item->getId()] = $item;
        }

        fclose($fp);
    }

    public static function getInstance() {
        if (!isset(self::$instance)) {
            self::$instance = new ItemDao();
        }
        return self::$instance;
    }

    public function findById($item_id)
    {
        if (array_key_exists($item_id, $this->items)) {
            return $this->items[$item_id];
        } else {
            return null;
        }
    }

    public function setAside(OrderItem $order_item)
    {
        echo $order_item->getItem()->getName() . 'の在庫引当をおこないました<br>';
    }

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

 setAsideメソッドでは、引当をおこなう商品名とメッセージが表示されるだけですが、本来はデータベースにアクセスして在庫情報を更新するコードが記述されます。

 なお、商品情報は固定長データとしてファイルに登録されています。なお、データフォーマットの詳細は表を参照してください。

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

 次はOrderDaoクラスです。このクラスにはcreateOrderメソッドのみが宣言されています。このメソッドは、Orderオブジェクトを受け取り、注文情報を表示しています。本来はデータベースにアクセスし、注文情報を登録する処理が記述されることになります。

OrderDaoクラス(OrderDao.class.php
<?php
require_once 'Order.class.php';

class OrderDao
{
    public static function createOrder(Order $order) {
        echo '以下の内容で注文データを作成しました';

        echo '<table border="1">';
        echo '<tr>';
        echo '<th>商品番号</th>';
        echo '<th>商品名</th>';
        echo '<th>単価</th>';
        echo '<th>数量</th>';
        echo '<th>金額</th>';
        echo '</tr>';

        foreach ($order->getItems() as $order_item) {
            echo '<tr>';
            echo '<td>' . $order_item->getItem()->getId() . '</td>';
            echo '<td>' . $order_item->getItem()->getName() . '</td>';
            echo '<td>' . $order_item->getItem()->getPrice() . '</td>';
            echo '<td>' . $order_item->getAmount() . '</td>';
            echo '<td>' . ($order_item->getItem()->getPrice() * $order_item->getAmount()) . '</td>';
            echo '</tr>';
        }
        echo '</table>';
    }
}

 お待たせしました。それでは、Façadeクラスに相当するOrderManagerクラスを見てみましょう。

 OrderManagerクラスはクライアント側から注文処理をおこなうためのAPIを定義し、具体的な注文処理を隠蔽する役割を担います。orderメソッドがそれに相当します。

OrderManagerクラス(OrderManager.class.php
<?php
require_once 'Order.class.php';
require_once 'ItemDao.class.php';
require_once 'OrderDao.class.php';

class OrderManager
{
    public static function order(Order $order) {
        $item_dao = ItemDao::getInstance();
        foreach ($order->getItems() as $order_item) {
            $item_dao->setAside($order_item);
        }

        OrderDao::createOrder($order);
    }
}

 Façadeパターンを適用しない場合、これらの処理を呼び出すコードをクライアント側に記述する必要があります。これでは、利用側は「注文処理」でおこなう処理や順序を全て知っている必要がありクライアントとそれぞれの処理を担当するクラスの結びつきが強くなってしまいます。

 Façadeパターンを適用して「注文する」というAPI(ここではorderメソッド)を用意することで、クライアント側はそのAPIを使うだけのシンプルなコードなり、またFaçadeクラスだけに依存するコードになります。

 では、Façadeパターンを適用した場合のクライアント側のコードはどの様になるか、見てみましょう。

facade_clientクラス(facade_client.php
<?php
require_once 'Order.class.php';
require_once 'OrderItem.class.php';
require_once 'ItemDao.class.php';
require_once 'OrderManager.class.php';

$order = new Order();
$item_dao = ItemDao::getInstance();

$order->addItem(new OrderItem($item_dao->findById(1), 2));
$order->addItem(new OrderItem($item_dao->findById(2), 1));
$order->addItem(new OrderItem($item_dao->findById(3), 3));

/**
 * 注文処理はこの1行だけ
 */
OrderManager::order($order);

 クライアント側ではOrderオブジェクトに商品を追加し、注文処理を実行しています。実際の注文処理が1行で実現されていることに注目してください。

注文処理に要するコード
<?php
        :
/**
 * 注文処理はこの1行だけ
 */
OrderManager::order($order);

 最後にFaçadeパターン適用後のクラス図を確認しておきましょう。

f:id:shimooka:20141208183622p:image

適用例の実行結果

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

f:id:shimooka:20141208183623p:image

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

 Façadeパターンは非常に大きな「カプセル化をおこなうパターンです。

 クライアントの視点でサブシステムを見てみると、Façadeクラスで提供された統一APIのみが見えます。ここで、サブシステム自体を「非常に大きなクラス」と捉えると、提供された統一APIメソッドに相当します。そのAPIの向こうには何やら複雑なものがあるのですが、クライアントからは見えませんし、意識する必要もありません。実際には、Façadeクラスの内部では背後にあるサブシステム内部のクラス群を使って、本来クライアントがおこなうべき複雑な処理をおこないます。

 つまりFaçadeパターンは、クラスの集まりであるサブシステムに対してカプセル化をおこなっていると言えます。カプセル化とは、データとその操作をまとめて「オブジェクト」として定義し、オブジェクト内部の動作や構造を隠蔽することですが、Façadeパターンではサブシステムの内部状態や構造、またその複雑さを見事に隠蔽しています。

関連するパターン

 プラットフォームに依存するクラスを隠蔽するために、Façadeパターンの代わりに利用される場合があります。

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

 Façadeパターンはサブシステムを利用するためのAPIを抽出しますが、Mediatorパターンはクラスどうしのやりとり自体を抽出するパターンです。

まとめ

 ここでは複雑なクラス関係をシンプルにするための統一インターフェースを提供するFaçadeパターンについて見てきました。

[][]PHPによるデザインパターン入門 - Factory Method〜生成処理と使用処理を分離する

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

GoF本における分類

 生成+クラス

はじめに

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

 「factory」とはもちろん「工場」という意味です。パターン名に使われているほどですから、もちろんそれなりの理由があります。工場とは一般的に何かを製造する施設ですよね…ここまで言うと、もうお気づきかもしれません。

 では、なぜ「工場」という名前が使われているのか、これから説明していきます。

たとえば

 たとえば、作曲家と作品のデータがCSVファイルとして保存されており、これを表示する場合を考えてみましょう。

 CSVファイルの1レコードには作曲家名とその作曲家の作品名が存在します。表示ルールとして、複数の作品を持つ作曲家の場合は初めに作曲家名を表示し、続けて作品名を表示するものとします。

Music.csv
ルートヴィヒ・ヴァン・ベートーヴェン,ピアノソナタ第23番ヘ短調「熱情」
ヴォルフガング・アマデウス・モーツァルト,魔笛
ヴォルフガング・アマデウス・モーツァルト,セレナーデト長調 K.525「小夜曲」
アントニン・ドヴォルザーク,交響曲第9番ホ短調「新世界より」

 ここで、簡単に以下のようなコードを書いてみます。

client.php
<html lang="ja">
<head>
<title>作曲家と作品たち</title>
</head>
<body>
<?php
    $handle = fopen ("Music.csv","r");
    $column = 0;
    $tmp = "";
    while ($data = fgetcsv ($handle, 1000, ",")) {
        $num = count ($data);
        for ($c = 0; $c < $num; $c++) {
            if ($c == 0) {
                if ($column != 0 && $data[$c] != $tmp) {
                    echo "</ul>";
                }
                if ($data[$c] != $tmp) {
                    echo "<b>" . $data[$c] . "</b>";
                    echo "<ul>";
                    $tmp = $data[$c];
                }
            }else {
                echo "<li>";
                echo $data[$c];
                echo "</li>";
            }
        }
        $column++;
     }
    echo "</ul>";
    fclose ($handle);
?>
</body>
</html>

 client.phpでは、CSVファイルを読み込みながらHTMLを出力しています。よくありがちなコードですね。特に問題はないでしょう。

 しかし世の中そうは簡単にいかないものです。たとえば、次のような修正をおこなう必要が出てきたとします。

  • CSV形式のデータを利用する他にXML形式のデータをサポートする
  • 表示はCSVの場合と同じとする
  • 読み込み、表示に利用するデータファイルは外部から渡される
  • CSVXMLデータのどちらを利用するかは、渡されたファイルの拡張子判断する

 単純に考えると、if文を使ってCSVファイルなのかXMLファイルを判定しながら、データの読み込みと表示をおこなえば、ひとまずは要求を満たすことができるでしょう。 しかし、さらなる別形式のデータをサポートする必要が出てきた場合、またデータの読み込み、表示コードを別に書き、条件分岐して…と、繰り返していくうちに条件分岐の羅列になってしまい、メンテナンス性が悪くなる事がわかります。

 また、こうなってはデータの読み込み部分を再利用することは難しくなります。

 ここで考え方を変えてみましょう。CSV形式やXML形式のデータを利用して達成したい事は、データを表示することです。表示するためにはデータを読み込む必要があります。データの形式がなんであれデータを読み込み、表示する。この2つが共通な機能として存在します

 共通の機能といっても、当然具体的な読み込み方法や表示方法は共通ではありません。このコードの違いを埋める仕組みができれば、より分かりやすくメンテナンスしやすいコードになりそうです。

 このような状況で活躍するのが、Factory Methodパターンです。

Factory Methodパターンとは?

 Factory Methodパターンはオブジェクトの生成の方法に注目したパターンで、オブジェクトを生成するためのAPIを定義し、クラスの継承を使って生成されるオブジェクトを切り替えることを目的としています。

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

オブジェクトを生成するときのインタフェースだけを規定して、実際にどのクラスをインスタンス化するかはサブクラスが決めるようにする。Factory Methodパターンは、インスタンス化をサブクラスに任せる。

 Factory Methodパターンとは、その名の通り「工場」のような振る舞いをします。何を作る工場かというと、「クラスのインスタンス」という製品を製造する工場です。

 さて、この「Factory」で生成される「オブジェクト」はどのように実装されているのかは分かりませんが、どのように実装されていても、同様の「機能」を提供します。この同様の「機能」を実現するために「インターフェース」を定義し、Factoryで生成されるクラスが実装を行っています。言い換えるとFactoryで生成されるインスタンスはどのクラスのものであれ、同様の「機能」を持っていると言えます。

 Factory Methodパターンを利用することで、オブジェクトの利用側はどのインスタンスが生成されるのかを知る必要がなくなります。繰り返しになりますが、利用側が知る必要があるのは製品の「機能」であり、具体的にどのクラスのインスタンスであるかを知る必要はありません。

 また、通常、オブジェクトはnew演算子を使って生成されますが、Factory Methodパターンではその代わりとなるインスタンス生成用のクラスが用意されます。このため、Virtual Constructor(仮想的なコンストラクタ)と呼ばれる事もあります。

Factory Methodパターンの構造

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

f:id:shimooka:20141208182623p:image

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

  • Productクラス

 オブジェクト生成メソッド(工場)で生成されるオブジェクト(製品)のAPIを定義するクラスです。オブジェクト生成メソッドは、「factory method」とも呼ばれます。

  • ConcreteProductクラス

 Productクラスのサブクラスで、Productクラスで定義されたAPIを実装したクラスです。

  • Creatorクラス

 オブジェクト生成メソッドを提供するクラスです。このメソッドは、Product型のオブジェクトを返します。また、あるConcreteProductオブジェクトを返すために、デフォルトの実装がなされる場合もあります。

  • ConcreteCreatorクラス

 Creatorクラスを継承したサブクラスです。ConcreteProductクラスのインスタンスを返します。

Factory Methodパターンのメリット

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

 複数のオブジェクトを扱う場合、if文やswitch文を使ってオブジェクトの生成コードを記述し、それらを利用するコードも同じコード内に記述してしまいがちです。こういった場合、オブジェクト生成のコードと利用側のコードを分けておくと、後々のメンテナンスが楽になります。

 Factory Methodパターンは、「オブジェクト生成側」と「オブジェクトの利用側」を分離するパターンです。「オブジェクト生成側」はCreatorクラスとConcreteCreatorクラス、「オブジェクト利用側」はクライアント側のコードがそれぞれ担います。その間をやりとりするオブジェクトが、ProductクラスとConcreteProductクラスになります。これにより、生成側はProductクラスを返すコード、一方の利用側はProductクラスを利用するコードを記述するだけで良くなり、それぞれの処理内容に専念することができます。

 Factory Methodパターンを使用することで、オブジェクトの利用側とオブジェクトの結びつきを低くする事ができます。これは、利用側でオブジェクトを直接生成しない、つまり、利用側のコードに「new クラス名」と直接書かなくてすむ、ということを意味します。この結果、利用側とオブジェクトの結びつきがゆるくなります。たとえば、生成するクラスの種類や生成手順が変更された場合でも、ファクトリ側を手直しするだけですみます。

Factory Methodパターンの適用

 ここでは冒頭に出てきたデータを表示する例にFactory Methodパターンを適用してみましょう。

 まずは、CSVデータとXMLデータを扱うクラスの共通機能をインターフェースとして定義しましょう。ここではReaderとします。このクラスでは共通機能であるデータの読み込みと表示を行うメソッドを定義します。

Reader.class.php
<?php
/**
 * 読み込み機能を表すインターフェースクラスです
 */
interface Reader {
    public function read();
    public function display();
}

 次に、CSVデータを扱うクラスCSVFileReaderを作成し、このクラスでReaderインターフェースを実装します。

CSVFileReader.class.php
<?php
require_once("Reader.class.php");

/**
 * CSVファイルの読み込みを行なうクラスです
 */
class CSVFileReader implements Reader
{
    /**
     * 内容を表示するファイル名
     *
     * @access private
     */
    private $filename;

    /**
     * データを扱うハンドラ名
     *
     * @access private
     */
    private $handler;

    /**
     * コンストラクタ
     *
     * @param string ファイル名
     * @throws Exception
     */
    public function __construct($filename)
    {
        if (!is_readable($filename)) {
            throw new Exception('file "' . $filename . '" is not readable !');
        }
        $this->filename = $filename;
    }

    /**
     * 読み込みを行ないます
     */
    public function read()
    {
        $this->handler = fopen ($this->filename, "r");
    }

    /**
     * 表示を行ないます
     */
    public function display()
    {
        $column = 0;
        $tmp = "";
       while ($data = fgetcsv ($this->handler, 1000, ",")) {
            $num = count ($data);
            for ($c = 0; $c < $num; $c++) {
                if ($c == 0) {
                    if ($column != 0 && $data[$c] != $tmp) {
                        echo "</ul>";
                    }
                    if ($data[$c] != $tmp) {
                        echo "<b>" . $data[$c] . "</b>";
                        echo "<ul>";
                        $tmp = $data[$c];
                    }
                }else {
                    echo "<li>";
                    echo $data[$c];
                    echo "</li>";
                }
            }
            $column++;
        }
        echo "</ul>";
        fclose ($this->handler);
    }
}

 同様に、XMLデータを扱うクラスXMLFileReaderも作成しましょう。

XMLFileReader.class.php
<?php
require_once("Reader.class.php");

/**
 * XMLファイルの読み込みを行なうクラスです
 */
class XMLFileReader implements Reader
{
    /**
     * 内容を表示するファイル名
     *
     * @access private
     */
    private $filename;

    /**
     * データを扱うハンドラ名
     *
     * @access private
     */
    private $handler;

    /**
     * コンストラクタ
     *
     * @param string ファイル名
     * @throws Exception
     */
    public function __construct($filename)
    {
        if (!is_readable($filename)) {
            throw new Exception('file "' . $filename . '" is not readable !');
        }
        $this->filename = $filename;
    }

    /**
     * 読み込みを行ないます
     */
    public function read()
    {
        $this->handler = simplexml_load_file($this->filename);
    }

    /**
     * 文字コードの変換を行います
     */
    private function convert($str)
    {
        return mb_convert_encoding($str, mb_internal_encoding(), "auto");
    }

    /**
     * 表示を行ないます
     */
    public function display()
    {
        foreach ($this->handler->artist as $artist) {
            echo "<b>" . $this->convert($artist['name']) . "</b>";
            echo "<ul>";
            foreach ($artist->music as $music) {
                echo "<li>";
                echo $this->convert($music['name']);
                echo "</li>";
            }
            echo "</ul>";
        }
    }

}

 次にFactoryクラスを作成しましょう。ここではReaderFactoryとします。このクラスはReaderクラスのインスタンスを生成するクラスになります。

 ReaderFactoryクラスは、どの形式が指定された場合にどのReaderクラスのインスタンスを生成するかを判断します。

 今回はファイルの拡張子判断するのでしたね。ファイル名が「〜.csv」の場合はCSVデータであり、「〜.xml」の場合はXMLデータとします。

ReaderFactory.class.php
<?php
require_once('Reader.class.php');
require_once('CSVFileReader.class.php');
require_once('XMLFileReader.class.php');

/**
 * Readerクラスのインスタンス生成を行なうクラスです
 */
class ReaderFactory
{
    /**
     * Readerクラスのインスタンスを生成します
     */
    public function create($filename)
    {
        $reader = $this->createReader($filename);
        return $reader;
    }

    /**
     * Readerクラスのサブクラスを条件判定し、生成します
     */
    private function createReader($filename)
    {
        $poscsv = stripos($filename, '.csv');
        $posxml = stripos($filename, '.xml');

        if ($poscsv !== false) {
            $r = new CSVFileReader($filename);
            return $r;
        } elseif ($posxml !== false) {
            return new XMLFileReader($filename);
        } else {
            die('This filename is not supported : ' . $filename);
        }
    }
}

 それでは、最初にお見せしたclient.phpを変更しましょう。CSVファイルやXMLファイルの読み込み部分を、今回作成したクラスを利用するように変更します。

factory_client.php
<?php
require_once('ReaderFactory.class.php');
?>
<html lang="ja">
<head>
<title>作曲家と作品たち</title>
</head>
<body>
<?php
    /**
     * 外部からの入力ファイルです
     */
    $filename = 'Music.xml';

    $factory = new ReaderFactory();
    $data = $factory->create($filename);
    $data->read();
    $data->display();
?>
</body>
</html>

 どうでしょうか?利用側のコードが非常にすっきりした事がわかると思います。

今後他に様々な形式のデータを利用する場合やDBからデータを取得したりインターネットを通じてデータをやり取りする場合、クライアント側のコードを変更する必要がなくなります。

 なお、Factory Methodパターンを適用したサンプルのクラス図は次のようになります。

f:id:shimooka:20141208182801p:image

適用例の実行結果

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

f:id:shimooka:20141208182719p:image

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

 Factory Methodパターンは「継承」を利用しているパターンです。

 ProductクラスとConcreteProductクラス、CreatorクラスとConcreteCreatorクラス、それぞれの間で継承関係があります。親クラスであるProductクラスとCreatorクラスは、親クラスどうしでどういった連携を行うかを決めます。具体的には、「オブジェクト生成メソッドからProduct型のオブジェクトを返す」といったものです。その具体的な実装は、それぞれのサブクラスであるConcreteProductクラスとConcreteCreatorクラスに任せています。この

処理の大枠を親クラスで規定し、具体的な処理内容をサブクラスに任せる

という部分は、まさにTemplate Methodパターンとなっています。

関連するパターン

  • Template Methodパターン

 Factory Methodパターンは、Template Methodパターンの代表的な適用です。通常、親クラスで処理の大枠を定義したメソッド(template methodと言います)が、factory methodになります。

 Creatorクラスは、Singletonパターンとして作られることが多くあります。これは、プログラム内で同じ工場が複数必要になることがほとんどないためです。

 Abstract Factoryパターンもfactory methodを使って実装されることが多いパターンです。

まとめ

 ここでは、オブジェクトの生成処理と使用処理を分離するFactory Methodパターンについて見てきました。

2014-12-12

[][]PHPによるデザインパターン入門 - Adapter〜APIを変更する

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

GoF本における分類

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

はじめに

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

 adaptという単語には「適合させる」とか「なじませる」「適応させる」という意味があります。つまり、adapterとは「適合させるもの」という意味になります。

 日本語でも、「アダプタ」という言葉はあちこちで聞く言葉だと思います。ACアダプタネットワークアダプタ、一昔前に全盛を誇ったISDN回線で必要になるターミナルアダプタTA)などいろいろあります。

 デザインパターンの1つ、Adapterパターンは、APIの異なるクラスどうしを「適合させる」パターンです。

たとえば

 ここでは、すでに作成済みで過去に充分テストされており、多くの利用実績のあるクラスを再利用する場面を考えてみましょう。

 このクラスは、次のようなクラスです。

ShowFile.class.php
<?php
/**
 * 指定されたファイルの内容を表示するクラスです
 */
class ShowFile
{
    /**
     * 内容を表示するファイル名
     *
     * @access private
     */
    private $filename;

    /**
     * コンストラクタ
     *
     * @param string ファイル名
     * @throws Exception
     */
    public function __construct($filename)
    {
        if (!is_readable($filename)) {
            throw new Exception('file "' . $filename . '" is not readable !');
        }
        $this->filename = $filename;
    }

    /**
     * プレーンテキストとして表示します
     */
    public function showPlain()
    {
        echo '<pre>';
        echo htmlspecialchars(file_get_contents($this->filename), ENT_QUOTES, mb_internal_encoding());
        echo '</pre>';
    }

    /**
     * キーワードをハイライトして表示します
     */
    public function showHighlight()
    {
        highlight_file($this->filename);
    }
}

 このShowFileクラスには、コンストラクタと2つのメソッドshowPlainメソッドとshowHighlightメソッドが定義されています。コンストラクタは、引数としてファイル名を1つ受け取ります。showPlainメソッドは、コンストラクタ引数として渡されたファイル名のファイル内容をそのまま表示します。一方のshowHighlightメソッドは、showPlainメソッドと同じく、ファイルの内容を表示しますが、キーワードハイライト表示します。

 このクラスを直接利用するクライアント側コードは、次のようになります。

adapter_sample_client.php
<?php
require_once 'ShowFile.class.php';

/**
 * ShowFileクラスをインスタンス化する。
 * 内容を表示するファイルは、「ShowFile.class.php」
 */
try {
    $show_file = new ShowFile('./ShowFile.class.php');
}
catch (Exception $e) {
    die($e->getMessage());
}

/**
 * プレーンテキストとハイライトしたファイル内容をそれぞれ
 * 表示する
 */
$show_file->showPlain();
echo '<hr>';
$show_file->showHighlight();

 ShowFileクラスをインスタンス化し、それぞれのメソッドを呼び出せば、問題なく利用することができます。

 しかし、すでに次のインターフェースが宣言されている場合を考えてみましょう。

DisplaySourceFile.class.php
<?php
interface DisplaySourceFile
{
    /**
     * 指定されたソースファイルをハイライト表示する
     */
    public function display();
}

 DisplaySourceFileインターフェースには、ソースファイルをハイライト表示するためのメソッドdisplayが宣言されています。一方、テキスト表示させるメソッドは定義されていません。これは利用側に「利用させない」ようにするためです。

 つまり、これを利用するクライアント側のコードは、次のようにしなければなりません。

displayメソッドを呼び出すコード
<?php$object->display();
        :

 しかし、ShowFileクラスにはdisplayメソッドは定義されていません。何らかの方法で、クライアント側が利用できるよう、displayメソッドを用意してやる必要があります。

 また、ShowFileクラスで定義されているshowPlainメソッドを、何らかの方法でクライアント側から隠す必要もあります。

 まずは、ShowFileクラスをコピー&ペーストして、新しいクラスを作成する方法を考えてみます。確かに最も簡単な解決方法かもしれません。しかし、対象となるクラスの数が多い場合は、いくらコピー&ペーストといえども大変な作業になります。さらに、コピー&ペースト作業中にコーディングミスをしてしまい、その原因調査に無駄な時間を取られてしまう可能性もあります。

 また、ShowFileクラスにバグなどによる修正やバージョンアップなどが行われた場合も、問題が発生します。予想できた人もいるかと思いますが、コピー&ペーストして作成したすべてのファイルに対して、ShowFileクラスと同じ修正を行う必要が出てきてしまいます。そうしない限り、新しいShowFileクラスに差し替えることができません。この問題は、重大なバグセキュリティホールの修正が行われた場合に大きな影響を与えます。

 次に、ShowFileクラスにdisplayメソッドを追加修正する方法を考えてみます。これもかなり簡単な解決方法です。場合によっては、うまくいきそうです。

ShowFile_bad.class.php
<?php
require_once 'DisplaySourceFile.class.php';

/**
 * 指定されたファイルの内容を表示するクラスです
 */
class ShowFile implements DisplaySourceFile
{
    /**
     * 内容を表示するファイル名
     *
     * @access private
     */
    private $filename;

    /**
     * コンストラクタ
     *
     * @param string ファイル名
     * @throws Exception
     */
    public function __construct($filename)
    {
        if (!is_readable($filename)) {
            throw new Exception('file "' . $filename . '" is not readable !');
        }
        $this->filename = $filename;
    }

    /**
     * プレーンテキストとして表示します
     */
    public function showPlain()
    {
        echo '<pre>';
        echo htmlspecialchars(file_get_contents($this->filename),
                             ENT_QUOTES, mb_internal_encoding());
        echo '</pre>';
    }

    /**
     * キーワードをハイライトして表示します
     */
    public function showHighlight()
    {
        highlight_file($this->filename);
    }

    /**
     * キーワードをハイライトして表示します
     * DisplaySourceFileインターフェースの実装
     */
    public function display()
    {
        highlight_file($this->filename);
    }
}

 しかし、この方法は充分にテストされ使用実績があるクラスを変更することになります。ShowFileクラスがもっと複雑な場合、この方法を採用することで、ShowFileクラスの動作確認テストをすべて行わなければならなくなります。また、再利用する上での重要事項である使用実績が失われることになります。

 また、継承だけではshowPlainメソッドを隠すことができません。DisplaySourceFileインターフェースの意図と異なり、ShowFileクラスが提供する他のメソッドクライアント側から実行することができてしまいます。

 さらに、この場合もコピー&ペーストの場合と同様、バージョンアップの際に問題になります。ShowFileクラスのコードを修正していますので、バージョンアップされたコードを再度追加修正するなどの対応が必要になります。

 通常、すべてのコードをゼロからプログラミングするとは限りません。この例のように、今までに作成したコードやクラスを再利用することがよくあります。開発生産性品質の向上を目的として、幅広くコードの再利用が行われています。特に、ShowFileクラスのように、利用実績もあり、テストも充分行われているコードであれば、再利用しない手はありません。しかも、再利用するコードについては一切変更を加えないで、必要となる機能を提供できるよう変更したい…

 Adapterパターンは、まさにこのために存在するパターンです。

Adapterパターンとは?

 Adapterパターンはクラスやオブジェクトの構造に注目したパターンで、APIの異なるクラスどうしを繋ぎ合わせること目的としています。

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

あるクラスのインターフェースを、クライアントが求める他のインターフェースへ変換する。Adapterパターンは、インターフェースに互換性のないクラス同士を組み合わせることができるようにする。

 たとえば、すでに存在しているクラスを再利用する場合を考えてみましょう。このクラスは充分にテストされており、色々な場面で使用された実績を持っているクラスです。しかし、利用側が期待するAPIと異なったAPIが提供されているため、利用側を変更するには多大な労力を余儀なくされる事が予想されます。逆に、既存のクラスを変更することも考えられますが、それまでの利用実績や信頼性が損なわれてしまうことになります。

 このような場合、既存クラスと利用側、2つの異なるAPIを吸収してやるアダプタを用意してやることで、変更範囲をおさえてクラスどうしを結合することができます。

Adapterパターンの構造

 Adapterパターンの実装には、2種類あります。ひとつは継承を使ったAdapterパターン、もうひとつは委譲を使ったAdapterパターンです。

継承を使ったAdapterパターンの構造は次のようになります。

f:id:shimooka:20141204155144p:image

一方、委譲を使ったAdapterパターンの構造は次のようになります。

f:id:shimooka:20141204155142p:image

 いずれの場合も、構成要素は次のとおりです。

  • Targetクラス

 clientが要求するAPIを提供します。

  • Adapteeクラス

 AdapterクラスによってAPIを変更される側のクラスです。具体的な処理を提供します。

  • Adapterクラス

 Adapteeが提供するAPIをTargetが提供するAPIに変換するクラスです。

コラム

 ここで「委譲」についてちょっと説明しておきます。委譲とは「権限などを他に任せて譲ること」です。ニュースでも「国・県から市町村への権限委譲」などと報道されますね。デザインパターンにおける「委譲」も同じ意味合いで、具体的な処理内容を他のクラスに任せる、という意味になります。

Adapterパターンのメリット

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

  • 既存のコードを修正することなく再利用できる

 Adapterパターンでは、既存のクラスを変更するのではなく、一枚皮をかぶせるようなクラスを作ります。このクラスに必要となるメソッドや機能を実装します。こうすることで、既存のクラスを一切変更することなく、新しいAPIとして提供することができます。この皮を被せることで既存のクラスを包み込んでいる様子から、Wrapper(ラッパー)パターンと呼ばれることもあります。

 また、既存のクラスを変更しないため、バグが発生した場合の切り分けが非常に楽になります。特に、既存のクラスにはバグがないことが分かっている場合は、新たに作成したAdapterクラスを集中的に調べるだけで良いことになります。

  • 利用側はアダプタの向こう側にある実装を意識する必要がない

 クライアント側は、アダプタの向こう側のクラスがどの様に実装されているかを意識する必要がありません

 利用側がアダプタの向こう側にある実装を意識しない、もしくは意識する必要がない、ということは、利用側をまったく変更せずにアダプタの向こう側にある実装を切り替えることができます。このことは、オブジェクト指向プログラミングを行う上で、非常に重要な位置付けにあります。

  • 公開するAPIを制限する

 Adapterパターンは異なるAPIを結びつけるパターンですが、結びつける際に意図的に公開するAPIを制限することもできます。

 逆に、公開されていないprotectedメソッドを利用するために、Adapterパターンを適用することもできます。

Adapterパターンの適用

 ここでは、冒頭に出てきたクラスにAdapterパターンを適用して再利用してみましょう。

 まずは、継承を使ったAdapterパターンです。DisplaySourceFileインターフェースとShowFileクラスの間に、アダプタの役割を担うクラスを用意します。ここでは、DisplaySourceFileImplクラスとしておきます。このクラスは、新しいAPIを提供するDisplaySourceFileインターフェースを実装し、既存のShowFileクラスを継承したクラスです。

 また、displayメソッドは、親クラスであるShowFileクラスのshowHighlightメソッドを使って実装します。

継承を使った場合のDisplaySourceFileImpl.class.php
<?php
require_once 'DisplaySourceFile.class.php';
require_once 'ShowFile.class.php';

/**
 * DisplaySourceFileを実装したクラス
 */
class DisplaySourceFileImpl extends ShowFile implements DisplaySourceFile
{
    /**
     * コンストラクタ
     */
    public function __construct($filename)
    {
        parent::__construct($filename);
    }

    /**
     * 指定されたソースファイルをハイライト表示する
     */
    public function display()
    {
        parent::showHighlight();
    }
}

 また、これを利用するクライアント側のコードは、次のようになります。

adapter_client.php
<?php
require_once 'DisplaySourceFileImpl.class.php';

/**
 * DisplaySourceFileImplクラスをインスタンス化する。
 * 内容を表示するファイルは、「ShowFile.class.php」
 */
$show_file = new DisplaySourceFileImpl('./ShowFile.class.php');

/**
 * プレーンテキストとハイライトしたファイル内容をそれぞれ
 * 表示する
 */
$show_file->display();

 ここまでのコードをクラス図にすると、次のようになります。adapter_clientからShowFileクラスは直接見えませんが、間接的にアクセスしていることが分かると思います。

f:id:shimooka:20141204155145p:image

 次に委譲を使ったAdapterパターンです。継承を使った場合と同様、DisplaySourceFileインターフェースとShowFileクラスの間に、アダプタの役割を担うDisplaySourceFileImplクラスを用意します。しかし、今度はShowFileクラスを継承せず、DisplaySourceFileインターフェースを実装したクラスになります。その代わり、DisplaySourceFileImplクラスのコンストラクタ内で、ShowFileクラスをインスタンス化します。そして、DisplaySourceFileImplクラスのメンバ変数として保持します。この「既存のクラスを包み込んでいる様子」がWrapperパターンとも呼ばれる理由となっています。

 また、displayメソッドは、メンバ変数に格納されたShowFileインスタンスのshowHighlightメソッドを使って実装します。

委譲を使った場合のDisplaySourceFileImpl.class.php
<?php
require_once 'DisplaySourceFile.class.php';
require_once 'ShowFile.class.php';

/**
 * DisplaySourceFileを実装したクラス
 */
class DisplaySourceFileImpl implements DisplaySourceFile
{
    /**
     * ShowFileクラスのインスタンスを保持する
     */
    private $show_file;

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

    /**
     * 指定されたソースファイルをハイライト表示する
     */
    public function display()
    {
        $this->show_file->showHighlight();
    }
}

 なお、クライアント側のコードは、継承を使ったAdapterパターンの場合とまったく同じです。

 こちらもクラス図を見てみましょう。継承を使った場合とほとんど同じですが、ShowFileクラスとDisplaySourceImplクラスの関係が違っています。

f:id:shimooka:20141204155143p:image

 ここで、Adapterパターンを適用するメリットを、おさらいします。

 継承、委譲いずれの場合も、既存クラスであるShowFileクラスには、一切変更が加わっていないことが分かります。つまり、ShowFileクラスの品質や利用実績を損なうことなく、再利用できたことを意味します。

 また、クライアント側に目的のメソッドのみを提供することに成功しています。継承、委譲いずれの場合も、DisplaySourceFileインターフェースを通すことで、不要なメソッド隠蔽しています。

 一方のクライアント側のコードは、DisplaySourceFileインターフェースに対してコーディングを行っておくことで、ShowFileクラスの実装を意識する必要がなくなります。つまり、ShowFileクラスの修正やバージョンアップが行われた場合も、クライアント側のコードを修正する必要はありません。新たに実装したDisplaySourceFileImplクラスのみ修正すれば良いからです。さらに、ShowFileクラスと同じ機能を持つ別のクラスや、より高機能、高性能なクラスに切り替える場合も、容易に行うことができます。

適用例の実行結果

 Adapterパターンを適用したサンプルの実行結果は、次のようになります。継承、委譲のいずれを使った場合も同じ結果になります。

継承を使ったサンプルのサンプルの実行結果は次のようになります。

f:id:shimooka:20141204155146p:image

また、委譲を使ったサンプルサンプルの実行結果も同様になります。

f:id:shimooka:20141204155141p:image

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

 Adapterパターンは、継承委譲ポリモーフィズムを利用したパターンです。

 これまで見てきたように、継承を使ったAdapterパターンの場合、既存のクラスを継承して、新しいAPIを持つクラスを実装します。一方、委譲を使ったAdapterパターンの場合、既存のクラスを包み込むクラスを用意し、そのクラスで新しいAPIを提供します。ここで、具体的な処理自体は、包み込んだ既存のクラスに任せてしまうような実装を行います。

 いずれの場合も、クライアント側にはインターフェースを通じて、新しいAPIを提供します。つまり、処理を実装したクラスは、クライアント側に提供されるインターフェースを実装しています。これにより、クライアント側からはインターフェースしか見えなくなり、その向こうにある処理の実装を意識する必要がなくなります。この「実装を意識することがなくなる」ため、実装を切り替える場合もアダプタに相当するクラスのみ修正するだけすみます。

関連するパターン

  • Bridgeパターン

 BridgeパターンはAdapterパターンの構造と非常によく似ていますし、本質的にも変わりありません。ただ、適用する理由が少々異なっています。

 Adapterパターンは「既存クラスを再利用するために繋ぎ合わせる」といった後天的な理由で用いられます。

 一方のBridgeパターンは「設計の段階で実装と機能を分離し、それぞれを繋ぎ合わせる」といった先天的な理由で導入されます。

まとめ

 ここではAdapterパターンについて見てきました。

 既存のクラスを包み込むためのクラスを用意し、そのクラスにAPIを用意しました。これにより、既存クラスを一切変更することなく、利用側に新しいAPIを提供できることが理解できたと思います。

[][]PHPによるデザインパターン入門 - Singleton〜いくつ作るかを制限する

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

GoF本における分類

 生成+オブジェクト

はじめに

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

 singletonとは「一枚札」「1つずつ起こるもの」といった意味を持つ単語です。

 Singletonパターンは、生成するオブジェクトの数を1つに制限するためのパターンです。では、なぜオブジェクトの数を制限する必要があるのでしょうか?

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

たとえば

 クラスのインスタンスはnew演算子を使って生成されます。たとえば、5回new演算子を使った場合、5つのインスタンスが生成されます。当然、1000回実行すると1000個のインスタンスが生成されます。

 しかし、インスタンスを生成するという処理は、コストがかかる処理です。オブジェクトの使いまわしをしないで毎回newするのは、大きなコストがかかってしまうことを意味します。

 また、「どうしてもインスタンスを1つしか生成したくない」といった場面も出てきます。たとえば、システムの設定を表現するクラスや、システム全体で一度読み込んだデータをキャッシュしておくクラスなどです。

 この場合、プログラミングする際に注意深くnew演算子を使うことで、1つしかインスタンスを生成させないようにすることもできます。しかし、それは「保証」されたものではありません。当然、何らかのミスや、それを知らない開発者が後からどんどんnewしていってしまうことも考えられます。

 開発者が意識しなくても、あるクラスのインスタンスが1つしか存在しないことを「保証する」ために使われるデザインパターン…それがSingletonパターンです。

Singletonパターンとは?

 Singletonパターンはオブジェクトの生成に関連するパターンで、生成されるインスタンスの数を制限することを目的としています。

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

あるクラスに対してインスタンスが1つしか存在しないことを保証し、それにアクセスするためのグローバルな方法を提供する。

 Singletonパターンを適用すると、インスタンスが1つしか生成されないことが保証されます。このため、開発者は「一度しかnewしてはならない」といった余計な事を考えずに済むようになります。

 なお、PHP5の場合、オブジェクトを複製するためのcloneキーワードが用意されています。Singletonパターンを使う場合、このcloneキーワードへの対策が必要となります。具体的には、PHP5から追加された__cloneメソッドをオーバーライドし、例外を発生、もしくは強制終了させます。

Singletonパターンの構造

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

f:id:shimooka:20141204153107p:image

 Singletonパターンには、Singletonクラスしかありません。このクラスのコンストラクタはprivateになっています。このため、他のクラスから直接Singletonインスタンスを生成する事はできません。その代わり、ただひとつのインスタンスを返すstaticメソッドが用意されます。また、自分自身のインスタンスSingletonインスタンス)を保持するためのstatic変数を内部に持っています。

Singletonパターンのメリット

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

 Singletonパターンは、自分自身のインスタンスを内部に保持しています。また、そのインスタンスへのアクセス手段も限られているため、他のクラスからインスタンスへアクセスするための方法は必然的に決まります。このため、クライアントからインスタンスへのアクセスを制御することができます。

 これまでの説明では同時に存在できるインスタンスは1つでしたが、