サイトトップ

Director Flash 書籍 業務内容 プロフィール

Adobe Flash非公式テクニカルノート

Iterator(イテレータ)パターン

ID: FN1010002 Product: Flash CS3 and above Platform: All Version: 9 and above/ActionScript 3.0

Iterator(イテレータ)は「反復子」とも訳され、繰返し処理を意味します。たとえば、データを納めた配列my_arrayからそのエレメントすべてを順に取出そうとするとき、forループによるつぎのような繰返し処理を行うのがお約束です。

for (var i:uint = 0; i < my_array.length; i++) {
  var myElement:Object = my_array[i];
  trace(myElement);
}

けれど、複数のデータのもち方はさまざまです。CSVやXMLデータになるかもしれません。あるいは、連結リストという手もあります。すると、Array.lengthプロパティや配列アクセス演算子[]は使えなくなるでしょう。データの取出し方に用いるプロパティやメソッドが統一できると、スクリプト(クラス)の使い回しがしやすくなります。デザインパターンIteratorは、その手法を示すものです。


01 Iteratorパターンのインターフェイス
クラスの設計で統一の仕様を定めるときは、備えるべきメソッドはインターフェイス(interface)として定義します。Iteratorパターンは、ふたつのインターフェイスから成ります。第1は、エレメントを納める容れ物となるクラスに実装するインターフェイスです。つぎのスクリプト001にIListとして定義しました。

スクリプト001■エレメントを納めるクラスに実装するインターフェイスIList
    // インターフェイス定義ファイル: IList.as
    // エレメントを納めるクラスに実装
  1. package {
  2.   public interface IList {
  3.     function iterator():IIterator;
  4.   }
  5. }

このインターフェイスを実装するクラスが備えるべきメソッドは、iterator()ひとつだけです(スクリプト001第3行目)。戻り値は、第2のインターフェイスとなるIIteratorを実装するクラスのインスタンスです。インターフェイスIIteratorは、つぎのスクリプト002のように定義されます。このインターフェイスが実装されるクラスは、容れ物のオブジェクト(IListを実装)から順にエレメントを返します。

スクリプト002■容れ物のオブジェクトからエレメントを順に返すクラスに実装インターフェイスIIterator
    // インターフェイス定義ファイル: IIterator.as
    // 容れ物のオブジェクトからエレメントを順に返すクラスに実装
  1. package {
  2.   public interface IIterator {
  3.     function hasNext():Boolean;
  4.     function next():Object;
  5.   }
  6. }

インターフェイスIIteratorが実装されるクラスは、容れ物のオブジェクトの中で参照すべきエレメント(ポインタ)をひとつ定めます。このインターフェイスに宣言されているメソッドはふたつです。メソッドhasNext()は、つぎに取出すエレメントがあるかどうかをブール(論理)値で返します(スクリプト002第3行目)。メソッドnext()は、現在参照しているエレメントを(Object型で)返します(第4行目)。加えて、メソッドの宣言からはわかりませんが、参照(ポインタ)をつぎのエレメントに移します。

エレメントの容れ物となるクラスにインターフェイスIListを実装し、IIteratorが実装されたエレメントを順に取出すクラスも定義できたとします。すると、容れ物のオブジェクトlistからすべてのエレメントを順に取出す処理はつぎのようになります。

var iterator:IIterator = list.iterator();
while (iterator.hasNext()) {
  var myElement:Object = iterator.next();
  // myElementに対する処理
}

まだクラスの中身はおろか、エレメントのデータをどうもたせるかさえ決めていません。それでも上記のスクリプトは、ふたつのインターフェイスが正しく実装されているかぎり、必ずすべてのエレメントを順に取出します。逆にいえば、データのもち方やクラスの具体的な内容を気にすることなく、少なくともすべてのデータの取出しについてはスクリプティングが考えられるということです。これが、Iteratorパターンの意義といえます。


02 インターフェイスを実装したクラスの定義
それでは、ふたつのインターフェイスをクラスに実装しましょう。まず、エレメントを納める容れ物のクラスです。エレメントは連結リストにします。連結リストはインデックスをもたないため、すべてのエレメントを取出す処理は必要に応じて備えなければなりません。そのために、Iteratorパターンを使うことが多いからです。

クラスは、エレメント用と容れ物用のふたつ定義します。テクニカルノート「連結リスト(linked list)」で定義したふたつのクラスを基本にしますので、それらの処理の中身についてはこのノートをご参照ください。エレメントをつくるクラスElementの定義は、つぎのスクリプト003のとおりです(前出ノートのスクリプト001参照)[*1]。このクラスはIteratorパターンに直接関わるものではなく、インターフェイスの実装もありません。

スクリプト003■連結リストのエレメントを生成するクラスElement
    // ActionScript 3.0クラス定義ファイル: Element.as
    // 連結リストのエレメントを生成
  1. package {
  2.   public class Element {
  3.     internal var prev:Element;
  4.     internal var next:Element;
  5.     public var data:Object;
  6.     public function Element(myData:Object) {
  7.       data = myData;
  8.     }
  9.   }
  10. }

Elementオブジェクトを納める連結リストのクラスは、以下のスクリプト004でクラスMyLinkedListとして定義します(前出ノートのスクリプト002参照)。インターフェイスIListを実装しますので、メソッドiterator()が定義されています(スクリプト004第30〜32行目)[*2]。メソッドは、自身の参照をコンストラクタメソッドMyLinkedListIterator()の引数に渡して、エレメント取出しのためのメソッドが備わったIIterator実装のオブジェクトを返します。

スクリプト004■連結リストのエレメントを納めて操作するクラスMyLinkedList
    // ActionScript 3.0クラス定義ファイル: MyLinkedList.as
    // 連結リストのエレメントを納めて操作する
  1. package {
  2.   public class MyLinkedList implements IList {
  3.     internal var first:Element;
  4.     internal var last:Element;
  5.     public function MyLinkedList() {}
  6.     public function push(myData:Object):void {
  7.       var newData:Element = new Element(myData);
  8.       if (last) {
  9.         last.next = newData;
  10.         newData.prev = last;
  11.         last = newData;
  12.       } else {
  13.         first = last = newData;
  14.       }
  15.     }
  16.     public function shift():Object {
  17.       if (first) {
  18.         var removingElement:Element = first;
  19.         first = removingElement.next;
  20.         if (first) {
  21.           first.prev = null;
  22.         } else {
  23.           last = null;
  24.         }
  25.         return removingElement.data;
  26.       } else {
  27.         return null;
  28.       }
  29.     }
  30.     public function iterator():IIterator {
  31.       return new MyLinkedListIterator(this);
  32.     }
  33.   }
  34. }

MyLinkedListインスタンスからエレメントを順に取出すためのクラスMyLinkedListIteratorには、以下のスクリプト005のようにインターフェイスIIteratorが実装されます。MyLinkedListインスタンスを引数に受取るコンストラクタメソッドのほかに 備えるべきメソッドは、hasNext()とnext()のふたつです。プロパティには、コンストラクタが受取ったMyLinkedListインスタンス(list)とその中の参照しているエレメント(currentElement)をもたせます。

スクリプト005■連結リストのエレメントを順に取出すためのクラスMyLinkedListIterator
    // ActionScript 3.0クラス定義ファイル: MyLinkedListIterator.as
    // 連結リストのエレメントを順に取出す
  1. package {
  2.   public class MyLinkedListIterator implements IIterator {
  3.     private var list:MyLinkedList;
  4.     private var currentElement:Element;
  5.     public function MyLinkedListIterator(myList:MyLinkedList) {
  6.       list = myList;
  7.       currentElement = list.first;
  8.     }
  9.     public function hasNext():Boolean {
  10.       if (currentElement) {
  11.         return true;
  12.       } else {
  13.         return false;
  14.       }
  15.     }
  16.     public function next():Object {
  17.       var oldElement:Element = currentElement;
  18.       if (oldElement) {
  19.         currentElement = currentElement.next;
  20.         return oldElement;
  21.       } else {
  22.         return null;
  23.       }
  24.     }
  25.   }
  26. }

スクリプト005第9〜15行目のhasNext()メソッドは、プロパティ(currentElement)に今参照しているエレメントがあるかどうかを確かめ、あればtrue、なければfalseを返します。第16行目〜24行目のnext()メソッドは、参照中のエレメントがあればそれを返すとともに、つぎのエレメントに参照を移します(第18〜20行目)。そして、参照がないときにはnullを返します(第21〜22行目)。

以上ふたつのインターフェイスと3つのクラスを用いた連結リスト(MyLinkedListオブジェクト)へのエレメントの追加・削除およびそれらすべての取出しは、つぎのようなフレームアクションで試せます(図001)。とくに、すべてのエレメントを取出す処理が、前述01「Iteratorパターンのインターフェイス」の最後に掲げた例と同じであることにご注目ください。

// テスト用フレームアクション
var list:MyLinkedList = new MyLinkedList();
// エレメントの追加
list.push(0);
list.push(1);
list.push(2);
list.push(3);
// 先頭エレメントの削除
trace("removed:", list.shift());
// すべてのエレメントの取出し
var iterator:IIterator = list.iterator();
while (iterator.hasNext()) {
  var myElement:Object = iterator.next();
  trace(myElement.data);
}

図001■連結リストへのエレメントの追加・削除およびエレメントすべての取出し
図001左図 図001右図

[*1] 前出ノートのスクリプト001と異なるのは、ふたつのプロパティprevとnextのアクセス制御の属性をpublicでなくinternalにしたことです。範囲を狭めただけですので、public属性のままでも動作は問題ありません。

[*2] iterator()メソッドが実装されたほかに前出ノートのスクリプト002と異なるのは、ふたつのプロパティfirstとlastのアクセス制御の属性をpublicからinternalにしたことです。public属性のままでも動作は変わりません。


03 XMLデータを取出す
ふたつのインターフェイスを実装した新たなクラスで、XMLデータを取出すとしたらとどうでしょうか。実際にXMLデータを用いる場合には、仕様にしたがって細かな処理が必要になります[*3]。けれど、本稿ではデータのもち方により扱いがどう変わるかを示すのがねらいです。そこで、XMLデータからXMLListオブジェクトを取出して、エレメントにします。

そこで、インターフェイスIListを実装するクラスMyXMLListのコンストラクタメソッドは、つぎのようにXMLオブジェクトと取出すノード名を引数にして呼出すことにします(図002)。

new MyXMLList(XMLオブジェクト, ノード名)

図002■XMLデータのノードを指定して取出す
図002左図

クラスMyXMLListは、以下のスクリプト006のように定義します。第5〜8行目のコンストラクタメソッドは、引数で受取ったXMLデータとノード名の文字列を、それぞれプロパティ(第3〜4行目)に納めます。

インターフェイスIListにより実装されるスクリプト006第15〜17行目のメソッドiterator()は、XMLデータからインターフェイスIIteratorが実装される後掲スクリプト007のクラスMyXMLListIteratorのコンストラクタメソッドに自身の参照を引数に渡して、MyXMLListIteratorインスタンスを返します。

スクリプト006第9〜11行目のgetAt()メソッドは、指定ノードの中から引数で渡されたインデックスのXMLデータを返します。また、第12〜14行目のlength()メソッドは、指定ノードの要素数を返します。

スクリプト006■XMLオブジェクトを受取って操作するクラスMyXMLList
    // ActionScript 3.0クラス定義ファイル: MyXMLList.as
    // XMLデータを操作する
  1. package {
  2.   public class MyXMLList implements IList {
  3.     public var myList:XML;
  4.     public var nodeName:String;
  5.     public function MyXMLList(_xml:XML, name:String) {
  6.       myList = _xml
  7.       nodeName = name;
  8.     }
  9.     public function getAt(n:uint):XML {
  10.       return myList[nodeName][n];
  11.     }
  12.     public function length():int {
  13.       return myList[nodeName].length();
  14.     }
  15.     public function iterator():IIterator {
  16.       return new MyXMLListIterator(this);
  17.     }
  18.   }
  19. }

インターフェイスIIteratorを実装するクラスMyXMLListIteratorは、スクリプト007のとおり定義しました。第5〜8行目のコンストラクタメソッドは、受取ったMyXMLListインスタンスをプロパティに納め、また参照エレメントのインデックスをもつプロパティは初期化します。

メソッドは、インターフェイスIIteratorを実装するふたつです。スクリプト007第9〜15行目のhasNext()メソッドは、現在のインデックスと要素数とを比べて、つぎに取出すエレメントがあるかどうかをブール(論理)値で返します。第16〜19行目のnext()メソッドは、現在参照中のエレメントを返すとともに、インデックスをひとつ進めます。

スクリプト007■XMLデータから要素を順に取出すクラスMyXMLListIterator
    // ActionScript 3.0クラス定義ファイル: MyXMLListIterator.as
    // XMLデータから要素を順に取出す
  1. package {
  2.   public class MyXMLListIterator implements IIterator {
  3.     private var list:MyXMLList;
  4.     private var index:uint;
  5.     public function MyXMLListIterator(myList:MyXMLList) {
  6.       list = myList;
  7.       index = 0;
  8.     }
  9.     public function hasNext():Boolean {
  10.       if (index < list.length()) {
  11.         return true;
  12.       } else {
  13.         return false;
  14.       }
  15.     }
  16.     public function next():Object {
  17.       var oldElement:XML = list.getAt(index++);
  18.       return oldElement;
  19.     }
  20.   }
  21. }

以上ふたつのクラスによりXMLデータから指定ノードの要素をすべて取出す処理は、つぎのようなフレームアクションで試せます(図003)。すべてのエレメントの取出しは、前項02「インターフェイスを実装したクラスの定義」の場合と同じく、前述01「Iteratorパターンのインターフェイス」の最後に掲げた例そのままです[*4]

// テスト用フレームアクション
var my_xml:XML = <cs5>
  <product suite="Web">
    <name>Flash</name>
    <price>699</price>
  </product>
  <product suite="Web">
    <name>Dreamweaver</name>
    <price>399</price>
  </product>
  <product suite="Design">
    <name>Photoshop</name>
    <price>699</price>
  </product>
  <product suite="Design">
    <name>Illustrator</name>
    <price>599</price>
  </product>
</cs5>;
// MyXMLListインスタンスの生成
var list:MyXMLList = new MyXMLList(my_xml, "product");
// すべてのエレメントの取出し
var iterator:IIterator = list.iterator();
while (iterator.hasNext()) {
  var myElement:Object = iterator.next();
  trace(myElement.name);
}

図003■XMLデータから指定ノードの要素をすべて取出す
図003左図 図003中図

このようにIteratorパターンにより、エレメントをすべて取出すという処理が、データやそのもち方を気にすることなくできるのです[*5]。上記フレームアクションで、iterator()メソッドの戻り値をインターフェイスIIterator型の変数に納めていることにもご注目ください。エレメントの取出しは、インターフェイスのhasNext()とnextのふたつのメソッドだけでできるからです。

[*3] XMLデータの扱いについては、akihiro kamijo「XML要素の操作」をご参照ください。

[*4] 実際には、取出したエレメントは、そのクラスがもつプロパティやメソッドを用いて操作するでしょう。その場合、取出したエレメントは、キャストする必要があります。

while (iterator.hasNext()) {
  var myElement:XML = iterator.next() as XML;
  trace(myElement.name);
}

[*5] そのほか、エレメントを取出す(IIterator実装の)クラスがデータをもつ(IList実装の)クラスと別に定義されるので、同じデータについてエレメントの順序など取出し方を変える(別のクラスを定義する)こともできます。


作成者: 野中文雄
作成日: 2010年10月12日


Copyright © 2001-2010 Fumio Nonaka.  All rights reserved.