【PHPデザインパターン】06_Iterator~順々にアクセスする


引用記事

この記事を書くきっかけになったブログです。

記事内の解説やソースコードは、こちらのブログと著者の公開リポジトリを参考にしています。

Do You PHP はてな〜[doyouphp][phpdp]PHPによるデザインパターン入門 - Iterator~順々にアクセスする

概要

  • オブジェクトに対する反復操作をおこなうための統一APIを提供する。
  • 具体的には、利用者にリスト(集約オブジェクト)の内部構造を隠したまま、それぞれの要素にアクセスさせるためのAPIを提供する。
  • 実装することにより、異なる内部構造を持つリストの要素に同じAPIでアクセスできる。
  • 「iterate」は「繰り返す」「反復する」という意味。

構成要素

Iteratorクラス

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

ConcreteIteratorクラス

  • Iteratorクラスのサブクラス。
  • リストの内部構造を把握し、それに依存する走査処理を実装する。

Aggregateクラス

  • Iteratorオブジェクトを返すAPIを提供するクラス。
  • クライアントの窓口に当たる。

ConcreteAggregateクラス

  • Aggregateクラスのサブクラス。
  • リスト固有のIteratorオブジェクトを返す。

SPLについて

SPL(Standard PHP Library)は標準的な問題を解決するためのインターフェイスやクラスを集めたものです。
PHP5.0.0以降はデフォルトで使用できるそうです。

php.net〜Standard PHP Library (SPL)

SPLにはイテレータが用意されており、オブジェクトを反復処理することができます。

php.net〜イテレータ

実演

著者のソースコードとほぼ同じです。

処理の流れ

  • 国名、大陸、言語の登録と出力を行う。
  • SPLのイテレータを利用する。
  • CountriesクラスはConcreteAggregateクラスに相当する。
  • Countriesクラスが実装しているIteratorAggregateインターフェースはAggregateクラスに相当する。
  • Countriesクラスで生成しているArrayObjectクラスは、オブジェクトを配列として扱うためのクラス。そこで配列用のiteratorクラスであるArrayIteratorクラスが利用される。
  • AsiaIteratorクラスはConcreteIteratorクラスに相当する。大陸がAsiaの国だけを出力できるようにフィルタリングを行う。

ファイル構造

 MyIterator
   ├── AsiaIterator.php
   ├── Countries.php
   ├── Country.php
   └── my_client.php

ソースコード

my_client.php

my_client.php
<?php
namespace DoYouPhp\PhpDesignPattern\Iterator\MyIterator;

require dirname(dirname(__DIR__)).'/vendor/autoload.php';

use DoYouPhp\PhpDesignPattern\Iterator\MyIterator\Country;
use DoYouPhp\PhpDesignPattern\Iterator\MyIterator\Countries;
use DoYouPhp\PhpDesignPattern\Iterator\MyIterator\Asia;

// Countriesインスタンスを作成する
// その中に配列の要素としてCountryインスタンスをセットする
$countries = new Countries();
$countries->add(new Country('Japan', 'Asia', 'Japanese'));
$countries->add(new Country('America', 'North America', 'English'));
$countries->add(new Country('China', 'Asia', 'Chinese'));

// イテレータを取得する
$iterator = $countries->getIterator();
// フィルタリングされたイテレータを取得する
$asia_iterator = new AsiaIterator($iterator);

// foreach構文を利用して配列の要素を取り出す
function dumpWithForeach(\Iterator $iterator)
{
    foreach ($iterator as $country) {
        echo '国名:'.$country->getName()."\t".'大陸:'.$country->getContinent()."\t".'言語:'.$country->getLanguage().'<br>'."\n";
    }
}

// Iteratorクラスのメソッドを利用して配列の要素を取り出す
// validメソッドで現在の要素が有効かチェックする
// 有効であればtrueが返り、処理が実行される
function dumpWithIterator(\Iterator $iterator)
{
    while ($iterator->valid()) {
        // currentメソッドで現在の要素の値を取得する
        $country = $iterator->current();
        echo '国名:'.$country->getName()."\t".'大陸:'.$country->getContinent()."\t".'言語:'.$country->getLanguage().'<br>'."\n";

        // nextメソッドでイテレータを前方に移動する
        $iterator->next();
    }
}

// イテレータのメソッドを利用する
echo 'これはIteratorクラスのメソッドです'.'<br>'."\n";
dumpWithIterator($iterator);

// foreach文を利用する
echo 'これはforeach文を利用しています'.'<br>'."\n";
dumpWithForeach($iterator);

// 異なるイテレータで要素を取得する
// FilterIteratorクラスの場合、配列の取り出しはforeach構文のみ利用できる
echo 'これはAsiaIteratorクラスでフィルタリングされforeach文を利用しています'.'<br>'."\n";
dumpWithForeach($asia_iterator);

Country.php

Country.php
<?php
namespace DoYouPhp\PhpDesignPattern\Iterator\MyIterator;

// 追加する要素の各値をセットする
class Country
{
    private $name;
    private $continent;
    private $language;

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

    // 各値を取得する
    public function getName()
    {
        return $this->name;
    }

    public function getContinent()
    {
        return $this->continent;
    }

    public function getLanguage()
    {
        return $this->language;
    }
}

Countries.php

Countries.php
<?php
namespace DoYouPhp\PhpDesignPattern\Iterator\MyIterator;

use DoYouPhp\PhpDesignPattern\Iterator\MyIterator\Country;

// IteratorAggregateクラスは外部イテレータを作成するためのインターフェイス
class Countries implements \IteratorAggregate
{
    // Countryインスタンスが配列の要素としてセットされる
    // ArrayObjectクラスのインスタンス
    private $countries;

    // ArrayObjectクラスはオブジェクトを配列として動作させる
    public function __construct()
    {
        $this->countries = new \ArrayObject();
    }

    // 配列に新しいCountryインスタンスを追加する
    public function add(Country $country)
    {
        $this->countries[] = $country;
    }

    // インターフェイスのメソッド
    // イテレータを取得して返す
    // 戻り値はIteratorクラスのオブジェクト
    public function getIterator()
    {
        return $this->countries->getIterator();
    }
}

AsiaIterator.php

AsiaIterator.php
<?php
namespace DoYouPhp\PhpDesignPattern\Iterator\MyIterator;

use DoYouPhp\PhpDesignPattern\Iterator\MyIterator\Country;

// FilterIteratorクラスはフィルタリングを行う抽象クラス
class AsiaIterator extends \FilterIterator
{
    // コンストラクタ
    public function __construct($iterator)
    {
        parent::__construct($iterator);
    }

    // FilterIteratorクラスの抽象メソッド
    // 配列の要素数だけ繰り返し実行される
    public function accept()
    {
        // getInnerIteratorメソッドで内部イテレータを取得する
        // currentメソッドで現在の要素の値を取得する
        // 両方ともFilterIteratorクラスのメソッド
        $country = $this->getInnerIterator()->current();

        // continentがAsiaの要素のみ抽出する
        return ($country->getContinent() === 'Asia');
    }
}