Processingで学ぶ 実践的プログラミング専門課程

第24回 デザインパターン(2) Iterator

この記事を読むのに必要な時間:およそ 4.5 分

Iteratorパターンのメリット

この仕組みがあれば,ConcreteAggregateクラスがどんな型やクラスのオブジェクト群を保持していても,それに関係なくClientクラスから,Iteratorインタフェイスの持つhasNextメソッドやnextメソッドでオブジェクト群の巡回が可能になります。仮にConcreteAggregateクラスが保持するオブジェクトの型が変更されても,それに対応してConcreteIteratorクラスのコードを修正しておけば,Clientクラスのコードでは,ConcreteAggregateクラスの保持するデータの巡回のためのコードに変更が必要ありません。

チームでコーディングしている際に,Iteratorパターンに従うことを共通理解しておけば,ConcreteAggregateクラス側の開発者も,それを用いるClientクラス側の開発者も,互いのコードに深く立ち入らずに独立して開発が続けられるのです。

例えば,

「このクラスはIteratorパターンに従っています」

と言えば,デザインパターンを知るプログラマは,そのクラスにはiteratorメソッドがあり,それを呼ぶとConcreteIteratorのオブジェクトを取得できるとわかります。後は,次々とオブジェクト群を巡回する際に,nextメソッドでオブジェクトを取得し,hasNextメソッドで次のオブジェクトが取得可能かを知ることができます。

Java言語では,既にConcreteIteratorにあたるオブジェクトを返すメソッドiteratorを実装しているクラスArrayListVectorなど)があります。Java言語において,iteratorメソッドを実装しなさいよ,ということを規定するAggregateインタフェイスにあたるのが,Java言語ではIterable<T>インタフェイスです。

ですから,Iteratorパターンの全体像を知らなくても,例えば拡張for文を使ったことがあるならば,Iteratorパターンを使ってオブジェクト群の巡回をしたことがあるということです。プログラマがConcreteIteratorクラスを書かなくても,Java言語側で用意していますから,Clientクラスからオブジェクトを巡回するコードを簡単に書けるのです。

Java言語やProcessingで使われているIteratorパターンを見る

次のサンプルコードを見てください。

import java.util.Arrays;
import java.util.Iterator;
ArrayList<String> A = new ArrayList(Arrays.asList("Tim Bray",
              "Brian Kernighan",
              "Jon Bentley",
              "Karl Fogel"));
println("**Iteratorパターンを使わない**"); // (1)
for(int i = 0; i < A.size(); i++){
  println(A.get(i));
}
println("**拡張for文。内部でIteratorパターンを使っている**"); // (2)
for(String v : A){
  println(v);
}
println("**明示的にIteratorパターンを使う**"); // (3)
Iterator it = A.iterator();
while(it.hasNext()){
  println(it.next());
}

Stringクラスのデータを納めるArrayListオブジェクトAを宣言し,4つの要素を納めます。これら4つの要素を順にコンソールに出力する3つの方法を見ていきます。

(1)は,昔ながらの配列の添字を指定した方法です。配列の添字となる変数iを用いて要素を指定します。

(2)は,拡張for文を用いて巡回する方法です。拡張for文は配列の添字やIteratorが表に現れません。拡張for文の向こうではIteratorパターンを使用しています。オブジェクトの集合がIteratorインタフェイスを実装しているという前提があるからこそ,拡張for文のような簡略表現が可能です。

(3)は,明示的にIteratorパターンを使用して巡回するループです。拡張for文は,内部では(3)のような処理を行っています。単純に先頭から末尾まで巡回するだけのために,(3)のようなコードを書くのは無駄です。(2)のように書くほうがシンプルで良いでしょう。

注目してもらいたいのは,(3)の場合には巡回と表示に使用するデータの型やクラスを指定していないということです。(1)も(2)も,表示する要素がString型のArrayListAである」とか,String型のオブジェクトvである」などと具体的な型やクラスを指定しています。しかし,(3)の場合には,AからConcreteIteratorオブジェクトへの参照を取得した後は,その参照だけを使用しています。巡回や表示にあたってはオブジェクトの型は不問になるため,ConcreteIteratorオブジェクトの取得先を切り替えれば,巡回と表示のためのコードは全く同じものが使えるというわけです。多様な型のデータをループ処理したい場合には,(3)のコードの書き方にメリットがあります。

あなたのコードにGoFのIteratorパターンを使う

それでは,GoFのIteratorパターンを具体的なコードにしてみます。連載第20回演習問題2の解答例に示したコードにIteratorパターンを適用します。その際,sketch名を改めてADrawToolとします。

Iteratorパターンを適用したsketch

これにより,ExStatechartDiagram2.pdedrawメソッド内のコードでは隠れていたIteratorが,ADrawTool.pdeでは表に引っ張りだされました。

ExStatechartDiagram2.pdeより

  for(Point p : dots){
    point((int)p.getX() * rate,(int)p.getY() * rate);
  }

ADrawTool.pdeより

  Iterator it = ca.iterator();
  while(it.hasNext()){
    Point p = (Point)it.next();
    point((int)p.getX() * rate,(int)p.getY() * rate);
  }

これらのサンプルコードでは,XY座標値をPointクラスのオブジェクトpに格納して点を描画するだけです。ExStatechartDiagram2.pdeでは,もともとArrayListが持っていたJava言語の持つIteratorパターンを利用した拡張for文を使ってデータを巡回するだけで事足りていました。

caはGoFによるIteratorパターンのAggregateインタフェイスを実装したConcreteAggregateクラスのオブジェクトですから,iteratorメソッドを持ちます。このiteratorメソッドを呼ぶと,Iteratorインタフェイスを実装したConcreteIteratorクラスのオブジェクトを返します。Iteratorインタフェイスのオブジェクトitが持つConcreteIteratorオブジェクトへの参照を用いて,whileループ内で各点の示すディスプレイウインドウ内の座標に点を描きます。

GoFによるデザインパターンのIteratorを忠実に適用したので,ADrawTool.pdeのコードは冗長になっています。これだけではIteratorパターンを見える化した利益はありません。

このADrawTool.pdeを発展させ,CADソフトウェアにしようとすると,単純な順方向イテレータだけでは不自由になるでしょう。例えば,拡張for文やJava言語に用意されているIteratorインタフェイスを使っていると,逆順の巡回ができません。ですから,より拡張されたIteratorインタフェイスがほしくなります。

それでは独自のIteratorパターンを用いるメリットがある活動に入ります。データ群を逆順に巡回するための,hasPrevious(⁠1つ前の要素はある?」⁠previous(⁠1つ前の要素を取得」⁠メソッドを作りましょう。逆順の巡回をスムーズに行うためには,データの末尾へ移動するメソッドがあると便利です。これをgoTailメソッドとしましょう。それならば,先頭からの巡回をスムーズに行うために,データの先頭へ移動するメソッドも用意しましょう。これをgoHeadメソッドとします。これらをインタフェイスIteratorに用意し,ConcreteIteratorへ実装しましょう。これらのメソッドを実装するクラスは,内部がどのように実装されていようとも,Client側にあたるADrawToolのコードからはIteratorインタフェイスを通じて,同じようにcaにアクセスできます。Iteratorパターンを利用する側のコードは,シンプルになり,なおかつIteratorパターンを適用したクラス側はコードの部品化度が高まるのです。

演習

演習1(難易度:middle)

sketchADrawToolIteratorインタフェイスにhasPrevious, previous, goHead, goTailの各メソッド宣言を用意し,ConcreteIteratorクラスにそれらを実装しましょう。

まとめ

  • Iteratorパターンのメリットと使い方を学びました。

学習の確認

それぞれの項目で,Aを選択できなければ,本文や演習にもう一度取り組みましょう。

  1. Iteratorパターンのメリットがわかりましたか?
    1. わかりました。自分のプログラミングにも活用したい。
    2. 解説を理解することはできたが,それほどメリットを感じない。
    3. 本文が理解できない。
  2. Iteratorパターンを使えるようになりましたか?
    1. 使えるようになった。自分のプログラミングにも活用できそうだ。
    2. 本文の例を理解することはできたが,自分のプログラミングに活用できる気がしない。
    3. 本文の例が理解できない。

参考文献

  • 『増補改訂版Java言語で学ぶデザインパターン入門』⁠結城浩 著,ソフトバンククリエイティブ)⁠
    • 誰もが認める最も分かりやすいデザインパターン入門書。本来は原典(GoF本)を勧めるべきところですが,本書は別格。
  • 『オブジェクト指向における再利用のためのデザインパターン』⁠Eric Gamma 著,ソフトバンククリエイティブ)⁠
    • デザインパターンの原典。別称『GoF本』サンプルコードがC++で書かれているため,ProcessingやJava言語のユーザにはハンディがあります。しかし結城浩氏の入門書を読みこなした後ならば本書にあたる価値があります。
  • 『Java デザインパターン徹底攻略』⁠日立ソフトウェアエンジニアリング⁠株⁠インターネットビジネス部 著,技術評論社)⁠
    • 流石の日立。前掲のGoF本の解説書で,サンプルはJava言語。絶版であることが惜しまれます。強く再版を望みます。

演習解答

  1. 追加したhasPrevious, previous, goHead, goTailが動作するかをチェックするテストコードConcreteIteratorTest.pdeを追加しています。

著者プロフィール

平田敦(ひらたあつし)

地方都市の公立工業高等学校教諭。趣味はプログラミングと日本の端っこ踏破旅行。やがては結城浩氏のような仕事をしたいと妄想している。

Twitter : @hirata_atsushi

コメント

コメントの記入