第3回 「SPL」でイテレーションを使いこなす

亀本 大地
アシアル株式会社

2008/10/07

Iteratorインターフェイスを使うメリット

 これまで、Iteratorインターフェイスとはどのようなものか、どのように利用するのかについて簡単に説明した。ここでイテレーション処理をIteratorクラスとして実装することのメリットを考えておきたい。

 前項でも触れたが、コレクションへ単純にアクセスしていく方法でイテレーション処理を実装すると、データを加工する処理をループ内に記述することになり、データ構造やオブジェクトの構造に依存した処理が埋め込まれる。このため、データの利用個所が増えれば増えるほど、データ構造やその処理方法が変化した場合に煩雑な問題を抱えてしまう。

 これを回避するために用いられるのが、データの取得・加工に関する処理をすべてオブジェクト内に隠蔽(いんぺい)してしまい、外部へはイテレーションに必要な繰り返し処理のためのAPIを提供する方法だ。

 SPLのIteratorインターフェイスは、外部への統一APIとしてのメソッドを定義したもので、この方法を容易に実現できる。また、先の例のとおり、Iteratorインターフェイスとforeachを組み合わせて用いることで明示的なメソッド呼び出しも不要となるため、呼び出し側をより簡潔な形で実装できる。

 このように、Iteratorインターフェイスを用いることでデータ構造とメインロジックを切り離すことが容易になり、コードの再利用性を高めることができるのだ。

IteratorAggregateインターフェイス

 イテレータを実装するうえで欠かせないもう一つの存在として、IteratorAggregateインターフェイスがある。

 これまで説明してきたIteratorインターフェイスは、実装したクラスそれ自体がイテレータとしての機能を持つものだ。これをそのまま拡張すると、あるデータ構造に依存する処理をまとめて記述することになる。

 しかし、実際には1種類のデータに対し1つの処理方法しかないとは限らず、むしろさまざまな処理のパターンを用意する方が一般的だ。データの処理にさまざまなパターンがある場合には、オブジェクトをデータ格納部とデータ処理部に分割し、データを格納するオブジェクトに対して集約的な関係になるよう実装すると非常に都合が良い。

 そのような関係を実装するために用意されているのが、IteratorAggregateインターフェイスだ。IteratorAggregateインターフェイスは、実装されたオブジェクト自体にはイテレータとしての機能を持たず、オブジェクト内部で別のIteratorオブジェクトを呼び出すことでイテレータとしての機能を実現する。

 以下に、IteratorAggregateインターフェイスを実装した例を示す。このクラスは、Iteratorの項で紹介したSquareクラスを呼び出すことでイテレータとして振る舞う。

<?php
class Square
{
 /*
  * Squareオブジェクトの実装(省略)
  */
}

class SquareAggregate implements IteratorAggregate
{
  protected $datas;

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

  public function getIterator() {
    return new Square($this->datas);
  }
}

$data = array(100, 200, 300);
$square = new SquareAggregate($data);

foreach ($square as $key => $val) {
  echo $key . ':' . $val . PHP_EOL;
}
var_dump($square);
list2

0:10000
1:40000
2:90000
object(SquareAggregate)#1 (1) {
  ["datas:protected"]=>
  array(3) {
    [0]=>
    int(100)
    [1]=>
    int(200)
    [2]=>
    int(300)
  }
}
結果

 結果を見ると分かるように、SquareAggregateクラス自体にはイテレータの機能を実装していないにもかかわらず、foreachのループに入った際にSquareクラスと同様のイテレーションが実行されている。

 これは、foreach内でIteratorAggregateインターフェイスのgetIteratorメソッドが自動的に呼び出され、その結果生成されたSquareクラスのインスタンスがイテレーションに利用されたためである。

 このように、IteratorAggregateインターフェイスを使うと外部のクラスを利用してイテレータとしての機能を持つことが可能になり、さらにforeachを利用することで呼び出し側での明示的な記述なしにイテレーションを実現できる。

 また、上述のgetIteratorメソッドで呼び出すクラスを動的に変更できる仕組みを実装すれば、さまざまなイテレータを利用できるようになり、処理方法のみを簡単に切り替えられる。

ArrayObject/ArrayIteratorクラス

 IteratorとIteratorAggregateの2つのインターフェイスを紹介したが、SPLではこれらのインターフェイスを継承したクラスが最初から数多く実装されている。ここでは、その好例といえるArrayObjectとArrayIteratorを紹介しよう。

 ArrayObjectとArrayIteratorは、SPLの各クラス群の中でも象徴的なものだといっていい。それぞれ、IteratorインターフェイスとIteratorAggregateインターフェイスを中心に、配列の動作に必要な各クラス、インターフェイスを継承したもので、Iteratorデザインパターンの実装となっている。

 ArrayObjectは、PHPのオブジェクトを本来PHPではプリミティブである配列と同様に扱えるようにしたものだ。オブジェクトであることに変わりはないため完全なエミュレーションにはなっていないが、基本的な挙動は配列とほとんど変わらない。

 ArrayIteratorはArrayObjectの外部イテレータで、それ自体もArrayObjectと同様に配列オブジェクトとして振る舞う。

注:ArrayObjectはArrayAccess、Countable、IteratorAggregateの各インターフェイスの実装で、ArrayIteratorはArrayAccess、Countable、SeekableIteratorの実装である。ArrayAccess、Countable、SeekableIteratorはすべてSPLで提供されるインターフェイスなので、個別に興味がある場合はSPL - Standard PHP Libraryを見てほしい。

 この2つは、多くの場合に対で使われる。最も単純に利用した場合の例を示そう。

<?php

$array = array(10,20,30,40,50);
$obj = new ArrayObject($array);

foreach ($obj as $key => $val) {
  echo $key . ':' . $val . PHP_EOL;
}

var_dump($obj);
list3

0:10
1:20
2:30
3:40
4:50
object(ArrayObject)#1 (5) {
  [0]=>
  int(10)
  [1]=>
  int(20)
  [2]=>
  int(30)
  [3]=>
  int(40)
  [4]=>
  int(50)
}
結果

 確かに$arrayの各要素が$objのプロパティとなり、順次アクセスしている様子が見て取れる。だが、さすがにこれだけではあまり差が分からないし、ArrayObjectにするだけ無駄に見えてしまうだろう。

 そこで、この配列から奇数番目の要素のみを抽出するようなロジックを、ArrayIteratorを拡張して実装してみよう。

<?php

class OddArrayIterator extends ArrayIterator
{
  public function next() {
    parent::next();
    if (parent::valid()) {
      parent::next();
    }
  }
}

$array = array(10,20,30,40,50);
$obj = new ArrayObject($array, 0, 'OddArrayIterator');

foreach ($obj as $key => $val) {
  echo $key . ':' . $val . PHP_EOL;
}

var_dump($obj);
list4

0:10
2:30
4:50
object(ArrayObject)#1 (5) {
  [0]=>
  int(10)
  [1]=>
  int(20)
  [2]=>
  int(30)
  [3]=>
  int(40)
  [4]=>
  int(50)
}
結果

 奇数番目(キーは0スタートである点に注意)の配列要素のみを取得できた。従来、このような実装はforeachの中に行われるのが一般的だったが、ArrayIteratorを使うことでイテレーションの処理内容をオブジェクト側に記述できるようになった。

 上の例のようにArrayObjectの第3引数としてイテレータのオブジェクト名を渡してやることで、利用するイテレータを切り替えられる。配列のさまざまなイテレーション実装の際に適切に、これらのオブジェクトを用いればコードの見通しも良くなり、メンテナンス性も担保できるだろう。

 今回は、SPLの代表的なインターフェイスやクラスを取り上げた。SPLにはまだまだ多くのクラスが用意されているので、ぜひともマニュアルなどを参照しながら理解を深め、活用してほしい。

2/2
 

Index
「SPL」でイテレーションを使いこなす
  Page1
標準PHPライブラリ「SPL」
Iteratorインターフェイス
Page2
Iteratorインターフェイスを使うメリット
IteratorAggregateインターフェイス
ArrayObject/ArrayIteratorクラス

PHP5で広がる! 開発環境

 PHP関連記事
例外処理の実装を把握する
PHP5で広がる! 開発環境(1)
 PHP4のサポートが終了し、いよいよPHP5への移行を視野に入れる時期が来た。PHP5の機能を生かした開発のポイントを紹介
クライアントPCに言語環境を入れる理由
Mac OS X+PHPでオールインワン環境(準備編)
 Webアプリ開発者に人気のMac OS X。効率的な開発のために複数バージョンのPHPを実行する環境を構築してみよう
PHPに押し寄せるリスクと国際化の波
PHPカンファレンス2008レポート(前編)
 PHP4のサポートが完全に終了する。多くの新機能が投入されるPHP5.3へ移行か、国際化対応で開発が遅れるPHP6を待つか
PHPによる大規模商用サービスの裏側
PHPカンファレンス2008レポート(中編)
 企業のWebアプリケーション開発現場で利用されるPHP。開発現場の裏側にはさまざまなドラマが隠されている
PHPユーザーは本当にほかの言語を知らないのか?
PHPカンファレンス2008レポート(後編)
 PHPは本当にダメな言語なのだろうか。Perl、Ruby、Python、Java、JavaScriptの使い手が白熱した議論を行った
  Coding Edgeフォーラムフィード  2.01.00.91


Coding Edge フォーラム 新着記事
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

>

Coding Edge 記事ランキング

本日 月間