まめーじぇんと@Tech

技術ネタに関して (Android, GAE, Angular). Twitter: @mame01122

Javascriptでデザインパターン (その7: Iterator)

イテレータパターンって?

Wikipediaによれば、”コンテナオブジェクトの要素を列挙する手段を独立させることによって、コンテナの内部仕様に依存しない反復子を提供することを目的とする。”とのこと。この説明は比較的分かりやすいかもですね。

僕の言葉で説明すれば、
・配列やListなどの要素をループで1つずつ取得、処理を行いたいときに使う。
・その際、配列やListを通常のfor文でまわすと、その配列やListと使う側との結合が密になりすぎてしまう。つまり、配列やListを異なる管理方法にした場合(Vectorにするとか)、それを使う側にも影響が及んでしまう。
・そこで、Iteratorというインタフェースを間に挟むことにより、使う側からは、使いたいデータの構造が何なのかを知ることなく使えるようにする(疎結合にする)

です。

これまでIteratorを使ってカウントすることはあっても自分で実装することはなかったので、今回、一度Javaで書いてからJavascriptにポーティングしてみました(Javaのコードは載せてませんが)

サンプルコード

では実際のコードです。まずはIteratorを定義します。使う側は、このIteratorを使うことで、実際のデータ構造を意識する必要がなくなります。
hasNext()は次の要素があればtrue、なければfalseを返す関数です。使う側がwhile文でループをまわすことを想定しています。

next()は現在のインデックスの要素を返して、インデックスを1つ進めるメソッドです。そのままですね。
コンストラクタで出てくるaggregateは後ほど説明します。

// Iterator + Concrete Iterator
var Iterator =  function(arg){

	this.index = 0;
	this.aggregate = arg;
};

Iterator.prototype = {
	hasNext : function(){
		if(this.aggregate.getSize() > this.index){
			return true;
		} else {
			return false;
		}
	},

	next : function(){
		var item = this.aggregate.getItemAt(this.index);
		this.index = this.index + 1;
		return item;
	}
};

続いてAggregateを定義します。

Aggregateに実際のデータが格納されています。今回だと、Elementが該当します。

そしてメソッドは、必須となるのがiterator()です。使う側はこのメソッドを呼ぶことによりイテレータインスタンスを取得します。その他のadd(), getSize(), getItemAt()は任意です。ユースケースによっては、例えば逆順で数えたりするインタフェースを追加してもいいかと思います。

// Aggregate + Concrete Aggregate
var Aggregate = function(){
	this.Element = [];
};

Aggregate.prototype = {
	iterator : function(){
		return new Iterator(this);
	},

	add : function(item){
		this.Element.push(item);
	},

	getSize : function(){
		return this.Element.length;
	},

	getItemAt : function(index){
		return this.Element[index];
	}

};

最後にこれまで定義したIteratorとAggregateを使う部分です。aggregateにaddしているのは準備部分です。その下でaggregateからiteratorインスタンスを取得し、ループをまわしてデータを取得します。

var aggregate= new Aggregate();
aggregate.add("Item A");
aggregate.add("Item B");
aggregate.add("Item C");
aggregate.add("Item D");
aggregate.add("Item E");

var iterator = aggregate.iterator();
while(iterator.hasNext()){
	var item = iterator.next();
	console.log("item: " + item);
}

実行結果は、

item: Item A
item: Item B
item: Item C
item: Item D
item: Item E

となっており、問題なく動いていることが確認できます。

データ構造が変わった場合は?

今回の例だと、aggregateの中身が変わることになります。
ここのデータ構造がかわってもaggregate内での修正(と、場合によってはIteratorも修正)に閉じるので、使う側は呼び方を変える必要がありません。


今回はJavascriptというより、イテレータのパターンの勉強になりました。