グローバルナビゲーションへ

本文へ

フッターへ

お役立ち情報Blog



今更ながらデザインパターンに入門してみた(Iteratorパターン編)

ソフトウェア開発の設計パターン集であるデザインパターンをご存知でしょうか。
筆者はデザインパターンをいつかは覚えておかないといけないなと思ってきましたが、中々重い腰が上がらずふわっとした知識だけにとどまっていました。

多くのソフトウェアではオブジェクト指向による設計・開発が取り入れられており、アーティスでも一部のプロダクトではオブジェクト指向による設計・開発を行っています。
改めてデザインパターンを知ることで、よりよい設計の指針の一助になる事を願っています。

※本連載は「Java言語で学ぶデザインパターン入門」を参考に、PHPで各デザインパターンを実装しなおした内容です。

GoFの『デザインパターン』で紹介された23個のパターンを、オブジェクト指向の初心者にもわかるようにやさしく解説。 すべてのパターンについて、Javaのサンプルプログラムを掲載。「デザインパターンQ&A」を新たに加筆。Amazon 増補改訂版Java言語で学ぶデザインパターン入門

デザインパターンとは?

ソフトウェア開発におけるデザインパターン(型紙(かたがみ)または設計パターン、英: design pattern)とは、過去のソフトウェア設計者が発見し編み出した設計ノウハウを蓄積し、名前をつけ、再利用しやすいように特定の規約に従ってカタログ化したものである。wikipediaより

第1章「Iterator」

Iteratorパターンとは、何かがたくさん集まっているときに、それを順番に指し示していき、全体をスキャンしていく処理を行うためのものです。 p.2 第1章 Iterator - 1つ1つを数え上げる

配列などの集合をループで回して処理していくケースをPHPで考えてみます。

<?php

$array = ['赤レンジャー', '青レンジャー', '黄レンジャー', '桃レンジャー', '緑レンジャー'];

for ($i = 0; $i < count($array); $i++) {
  echo $array[$i], PHP_EOL;
}

echo '5人揃って、5レンジャー', PHP_EOL;

実行結果

$ php loop-ranger.php
赤レンジャー
青レンジャー
黄レンジャー
桃レンジャー
緑レンジャー
5人揃って、5レンジャー

ループ処理を可能にし、コレクションのようにみせることで、上記のようなループ処理を抽象化し、パターン化したものを「Iterator」パターンと呼びます。 書籍では本棚にある本を数え上げるJavaでの実装例が紹介されています。

今回は書籍の実装例を参考に、PHPでIteratorパターンを実装してみます。

事前準備

複数のファイルを扱う事になるので、クラスのオートロードをcomposerで行います。

1. composerのpharファイルをダウンロードします

$ curl -Ss https://getcomposer.org/installer | php

2. composer.jsonファイルを作成しオートロードの設定を記述して保存します

{
    "autoload": {
        "psr-4": {
            "DesignPattern\\": "src/"
        }
    }
}

3. composerのコマンドを実行します

$ php composer.phar install

PHPで実装しなおしてみた

Aggregate(集約・集合体)

<?php

namespace DesignPattern\Iterator\Book;

interface Aggregate
{
    /**
     * @return Iterator
     */
    public function getIterator();
}

Iterator(反復処理を扱う)

<?php

namespace DesignPattern\Iterator\Book;

interface Iterator
{
    /**
     * @return bool
     */
    public function hasNext();

    /**
     * @return Aggregate
     */
    public function next();
}

BookShelf(Aggregateの実装)

<?php

namespace DesignPattern\Iterator\Book;

class BookShelf implements Aggregate, \Countable
{
    /**
     * @var array
     */
    private $books;

    public function __construct()
    {
        $this->books = [];
    }

    /**
     * @param  int $index
     * @return Book
     */
    public function getBookAt($index)
    {
        return $this->books[$index];
    }

    /**
     * @param  Book $book
     * @return void
     */
    public function appendBook(Book $book)
    {
        $this->books[] = $book;
    }

    /**
     * @return BookShelfIterator
     */
    public function getIterator()
    {
        return new BookShelfIterator($this);
    }

    /**
     * @return int
     */
    public function count()
    {
        return count($this->books);
    }
}

BookShelfIterator(Iteratorの実装)

<?php

namespace DesignPattern\Iterator\Book;

class BookShelfIterator implements Iterator
{
    /**
     * @var BookShelf
     */
    private $bookShelf;

    /**
     * @var int
     */
    private $index;

    /**
     * BookShelfIterator constructor.
     * @param BookShelf $bookShelf
     */
    public function __construct(BookShelf $bookShelf)
    {
        $this->bookShelf = $bookShelf;
        $this->index = 0;
    }

    /**
     * {@inheritdoc}
     */
    public function hasNext()
    {
        return $this->index < count($this->bookShelf);
    }

    /**
     * @return Book
     */
    public function next()
    {
        $book = $this->bookShelf->getBookAt($this->index);
        $this->index++;

        return $book;
    }
}

Book(数え上げられる対象)

<?php

namespace DesignPattern\Iterator\Book;

class Book
{
    /**
     * @var string
     */
    private $name;

    /**
     * Book constructor.
     * @param string $name
     */
    public function __construct($name)
    {
        $this->name = $name;
    }

    /**
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }
}

実行クライアント

<?php

require __DIR__.'/vendor/autoload.php';

use DesignPattern\Iterator\Book\BookShelf;
use DesignPattern\Iterator\Book\Book;

$bookShelf = new BookShelf();
$bookShelf->appendBook(new Book('第1版の本'));
$bookShelf->appendBook(new Book('第2版の本'));
$bookShelf->appendBook(new Book('第3版の本'));
$bookShelf->appendBook(new Book('第4版の本'));
$bookShelfIterator = $bookShelf->getIterator();

while ($bookShelfIterator->hasNext()) {
    $book = $bookShelfIterator->next();

    echo $book->getName(), PHP_EOL;
}

最終的なディレクトリ構造は以下になります。

├── composer.json
├── composer.phar
├── main.php
├── src
│   └── Iterator
│       └── Book
│           ├── Aggregate.php
│           ├── Book.php
│           ├── BookShelf.php
│           ├── BookShelfIterator.php
│           └── Iterator.php
└── vendor

実行結果

$ php main.php
第1版の本
第2版の本
第3版の本
第4版の本

SplIterator

実はPHPではIteratorインターフェースが既に定義されています。

PHPマニュアル Iteratorインターフェース

その他にもSPL(Standard PHP Library)には様々なIteratorの実装が組み込みで定義されています。

ArrayIterator

今回のサンプルコードのような処理をSplArrayIteratorで実装するとこのようなコードになります。

<?php

$ranger = new \ArrayObject(['赤レンジャー', '青レンジャー', '黄レンジャー', '桃レンジャー', '緑レンジャー']);

/**
 * @var \ArrayIterator $iterator
 */
$iterator = $ranger->getIterator();

while ($iterator->valid()) {
    echo $iterator->current(), PHP_EOL;
    $iterator->next();
}

echo '5人揃って5レンジャー', PHP_EOL;

実行結果

$ php ranger.php
赤レンジャー
青レンジャー
黄レンジャー
桃レンジャー
緑レンジャー
5人揃って5レンジャー

RecursiveIteratorIterator と RecursiveDirectoryIterator

ディレクトリを再帰的に走査するケースを考えてみます。

iteratorを使わない実装では再帰処理を実装し、再帰的にディレクトリを走査する実装方法があります。

<?php

function dirToArray($dir) {

   $result = array();

   $cdir = scandir($dir);
   foreach ($cdir as $key => $value) {
      if (!in_array($value, array(".",".."), true)) {
         if (is_dir($dir . DIRECTORY_SEPARATOR . $value)) {
            $result[$value] = dirToArray($dir . DIRECTORY_SEPARATOR . $value);
         } else {
            $result[] = $value;
         }
      }
   }

   return $result;
}

var_dump(dirToArray(__DIR__));

再帰的な処理は実装が難しく、確認が難しいのもあって辛みがすごいです・・・。

RecursiveIteratorIteratorとRecursiveDirecotoryIteratorを使って実装するとコードはこのようになります。

<?php

$iterator = new \RecursiveIteratorIterator(
  new \RecursiveDirectoryIterator(
      __DIR__,
      \RecursiveDirectoryIterator::SKIP_DOTS
    ),
    \RecursiveIteratorIterator::CHILD_FIRST
);

$result = [];
/** @var SplFileInfo $file */
foreach ($iterator as $file) {
  $result[] = $file->getPathname();
}

var_dump($result);

まとめ

書籍には以下の様な作者の記載があります。

どうしてIteratorパターンなんて面倒なものを考える必要があるのでしょう。配列だったらfor文でくるくる回せばいいじゃないですか。なぜ集合体の外にIterator役などというものを作る必要があるのでしょう。

大きな理由は、Iteratorを使う事で、実装とは切り離して、数え上げを行う事ができるからです。p.11 第一章 あなたの考えを広げるためのヒント

実装と切り離すことでIteratorのループがAggregateの実装には依存しなくなります。その結果Aggregateの実装を差し替え可能になる事が有用と感じました。

また、今回の例では取り扱うデータ構造が配列でしたが、コレクションとして振る舞うオブジェクトを走査したいといったケースでもIteratorパターンは有用そうです。

PHPにはその他にもSPLには実用的なSplIteratorがあります。 一度目を通して見てはいかがでしょうか。

PHPマニュアル イテレータ

この記事を書いた人

美髭公
美髭公事業開発部 web application engineer
2013年にアーティスに入社。システムエンジニアとしてアーティスCMSを使用したWebサイトや受託システムの構築・保守に携わる。環境構築が好き。
この記事のカテゴリ

FOLLOW US

最新の情報をお届けします