今更ながらデザインパターンに入門してみた(Iteratorパターン編)
ソフトウェア開発の設計パターン集であるデザインパターンをご存知でしょうか。
筆者はデザインパターンをいつかは覚えておかないといけないなと思ってきましたが、中々重い腰が上がらずふわっとした知識だけにとどまっていました。
多くのソフトウェアではオブジェクト指向による設計・開発が取り入れられており、アーティスでも一部のプロダクトではオブジェクト指向による設計・開発を行っています。
改めてデザインパターンを知ることで、よりよい設計の指針の一助になる事を願っています。
※本連載は「Java言語で学ぶデザインパターン入門」を参考に、PHPで各デザインパターンを実装しなおした内容です。
デザインパターンとは?
第1章「Iterator」
配列などの集合をループで回して処理していくケースを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インターフェースが既に定義されています。
その他にも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を使う事で、実装とは切り離して、数え上げを行う事ができるからです。p.11 第一章 あなたの考えを広げるためのヒント
実装と切り離すことでIteratorのループがAggregateの実装には依存しなくなります。その結果Aggregateの実装を差し替え可能になる事が有用と感じました。
また、今回の例では取り扱うデータ構造が配列でしたが、コレクションとして振る舞うオブジェクトを走査したいといったケースでもIteratorパターンは有用そうです。
PHPにはその他にもSPLには実用的なSplIteratorがあります。 一度目を通して見てはいかがでしょうか。
この記事を書いた人
- 2013年にアーティスに入社。システムエンジニアとしてアーティスCMSを使用したWebサイトや受託システムの構築・保守に携わる。環境構築が好き。
関連記事
最新記事
FOLLOW US
最新の情報をお届けします
- facebookでフォロー
- Twitterでフォロー
- Feedlyでフォロー