Shoeisha Technology Media

CodeZine(コードジン)

記事種別から探す

デザインパターンの使い方:Iterator

  • LINEで送る
  • このエントリーをはてなブックマークに追加
2009/05/13 14:00

 Iterator(反復)は、ありふれたコンピューティング処理の1つです。簡潔で、なおかつ一貫性と表現力のある実装方法が求められる状況には、反復はうってつけです。本稿では、Iteratorパターンのさまざまな使用例を紹介します。

目次

Iteratorパターンの例

 Iterator(反復)がデザインパターンだなんて冗談じゃない。オブジェクトのコレクションを順番に片付けるやり方なんて、プログラミングを覚え始めた頃から知ってるよ――そう思った人もいるのではないでしょうか。

 ただ、考えてもみてください。Iteratorをさまざまなデザインパターンの1つとして正式に認めた書籍『デザインパターン』が刊行されたのは1995年のことなのです。Javaはまだ普及しておらず、C++が主なオブジェクト指向言語だった時代です。オブジェクト指向開発そのものは、ほとんどのプログラマにとってまだ目新しいものでした。

 当時のC++コーダーは、コレクションに対して反復処理を行う仕組みを自作するのが一般的でした。例えば、prevメソッドとnextメソッドを書いて、リンクリスト内を前後に移動できるようにする、といった方法です。また、コレクションによっては、カプセル化されたデータ構造(配列など)を指すポインタを公開して、クライアント側でデータ構造を反復処理できるようにすることさえありました。多くの場合、反復処理を実現しようとすれば、基盤のコレクション実装について具体的な詳細情報を多量に公開する必要がありました。

 同じ時期に流行していたSmalltalkには、さまざまな内部イテレータが用意されていました。これはクロージャの使用によって実現された機能です。内部イテレータはコレクションの反復処理を制御するのに対して、外部イテレータはクライアントが反復処理を制御することを可能にします。一般に、外部イテレータは柔軟性を高めるためのもので、コレクションクラスの作成者が意図しなかったような反復処理をクライアント側で定義することを可能にします。それとは対照的に、内部イテレータはクライアントコード側の作業を簡略化するものです。

 Smalltalkプログラマはごく簡単に内部イテレータを利用できました。なんらかのコレクションを反復処理する必要が出てきた場合には、あらかじめ用意されている反復メカニズム(do:select:detect:reject:inject:into:)をいつでも使用できます。

 Javaでは、外部イテレータに対応するいくつかのコレクションクラスが用意されました。基底コレクションクラスであるVectorには、Enumerationオブジェクトを返すelements()メソッドが用意されています。クライアント側でこのEnumerationオブジェクトを利用して、コレクションの反復処理を実行できます。

Vector names = new Vector();
names.add("Yossarian");
names.add("Minderbinder");
names.add("Clevinger");
for (Enumeration e = names.elements(); e.hasMoreElements(); ) {
   String name = (String)e.nextElement();
   // ...
}

 当然ですが、この反復処理メカニズムがあるからといって、プログラマが昔ながらの反復処理を書けないわけではありません。

for (int i = 0; i < names.size(); i++) {
   String name = (String)names.get(i);
   // ...
}

 今でも一部の開発者は、この方式の反復処理を好むようです。パフォーマンスの点で、やや有利になる(可能性がある)と考えてのことです。しかし、これはオブジェクト指向に反するコードです。クライアント側が、基盤となるリスト表現形式を理解しなければ書けないコードだからです。Vectorを使用する場合は、そこまで知る必要はありません。また、プログラマがコレクションを順不同の構造(セットなど)に変えることに決めた場合、ループのコードを書き直す必要があります。

 Java 2のコレクションクラスフレームワークによって、インターフェースと反復子を使用する堅牢なコレクションクラスセットがJavaに導入されました。EnumerationがIteratorに置き換えられ、またメソッド名が簡略化されたため、コレクションをループ処理するコードがやや簡素になります。

List names = new ArrayList();
names.add("Yossarian");
names.add("Minderbinder");
names.add("Clevinger");

for (Iterator it = names.iterator(); it.hasNext(); ) {
   String name = (String)it.next();
   // ...
}

 図1に、JDK 1.2~1.4でArrayListとして実装されたIteratorパターンのUML表記を示します。ここでは図を見やすくするため、中間の抽象クラスとインターフェースを省きました。

図1 Iteratorパターン
図1 Iteratorパターン

 不思議なことに、今でもArrayListではなくVectorを選び、インターフェース型への代入という考え方を拒むJavaプログラマが見受けられます。知ってはいるけど、使わないという姿勢です。その理由が「10年来の習慣を変えるのは難しい」ではないとすれば、おそらく、Vectorを使うとデフォルトで有効になる同期のメカニズムが捨てられないのでしょう。しかし、これはコレクションフレームワークの同期ラッパーを使うことで解決できる問題です。私が耳にしたもう少し合理的な言い訳は、Vectorの増加量を制御したいから、というものです。これは新しいArrayListクラスから抜け落ちた機能です。

 リストの内部を移動する反復テクニックは、明らかに3種類では足りなかったようです。Java 5では、コレクションの内部を移動する別のメカニズムとして、強化版のforループが導入されました。

List<String> names = new ArrayList<String>();
names.add("Yossarian");
names.add("Minderbinder");
names.add("Clevinger");

for (String name: names) {
   // ...
}

 強化版のforループの利点は、クラスをパラメータ化された型にバインドできるため、クライアントコードでキャストする必要がなくなることです。さらにうれしいのは、Iterableインターフェースを実装したクラスを書くことで、強化版のforループのサポートを提供できることです。

class Menu implements Iterable<String> {
   private List<String> items = new ArrayList<String>();

   public void add(String item) {
      items.add(item);
   }

   @Override
   public Iterator<String> iterator() {
      return items.iterator();
   }
}

 MenuIterableを実装すると、クライアント側はすべてのメニュー項目を反復処理するfor-eachループを簡潔に記述できます。

Menu menu = new Menu();
menu.add("Beef Stroganoff");
menu.add("Filet Mignon");
menu.add("Rice with Tofu");

for (String item: menu) {
   // ...
}

 強化版のforループの構文は、少なくともJava 7で導入の可能性があるクロージャの登場までは、Javaで記述できるほとんど限界に近い簡潔な表現です。


  • LINEで送る
  • このエントリーをはてなブックマークに追加

著者プロフィール

  • japan.internet.com(ジャパンインターネットコム)

    japan.internet.com は、1999年9月にオープンした、日本初のネットビジネス専門ニュースサイト。月間2億以上のページビューを誇る米国 Jupitermedia Corporation (Nasdaq: JUPM) のニュースサイト internet.com や EarthWeb.c...

  • Jeff Langr(Jeff Langr)

    本格的なソフトウェアの開発に四半世紀以上携わってきたベテランのソフトウェア開発者。『Agile Java: Crafting Code With Test-Driven Development』(Prentice Hall、2005年)と、他の1冊の著書がある。『Clean Code』(Uncle...

バックナンバー

連載:デザインパターンの使い方

もっと読む

All contents copyright © 2005-2017 Shoeisha Co., Ltd. All rights reserved. ver.1.5