JavaにおけるIteratorデザインパターン
イテレーターデザインパターンは、行動パターンのひとつです。イテレーターパターンは、オブジェクトのグループをトラバースするための標準的な方法を提供するために使用されます。イテレーターパターンは、Javaコレクションフレームワークで広く使用されています。イテレーターインターフェースは、コレクションをトラバースするためのメソッドを提供します。
イテレータデザインパターン
GoFによると、イテレーターデザインパターンの目的は次の通りです。
下層の表現を公開せずに、集約オブジェクトの要素にアクセスする方法を提供します。
イテレータパターンは、コレクションを進行するだけでなく、要件に基づいた異なる種類のイテレータを提供することができます。イテレータデザインパターンは、コレクションを進行するための実際の実装を隠し、クライアントプログラムは単にイテレータメソッドを使用します。
イテレーターパターンの例。
シンプルな例でイテレーターパターンを理解しましょう。ラジオチャンネルのリストがあり、クライアントプログラムがそれらを一つずつまたはチャンネルの種類に基づいてトラバースしたい場合を考えてみましょう。例えば、一部のクライアントプログラムは英語のチャンネルにのみ興味があり、それらのみ処理したいとします。他の種類のチャンネルは処理したくありません。したがって、クライアントにチャンネルのコレクションを提供し、クライアントがチャンネルをトラバースし、処理するかどうかを決定するロジックを書くことができます。しかし、この解決策には多くの問題があります。例えば、クライアントがトラバースのためのロジックを考え出さなければならないため、そのロジックが正しいかどうかを保証することができません。さらに、クライアントの数が増えると、メンテナンスが非常に困難になります。ここで、イテレーターパターンを使用し、チャンネルの種類に基づいた反復を提供することができます。クライアントプログラムがチャンネルのリストにアクセスできるのはイテレーターを通してのみであることを確認する必要があります。実装の最初の部分は、コレクションおよびイテレーターのインターフェースの契約を定義することです。ChannelTypeEnum.java
package com.scdev.design.iterator;
public enum ChannelTypeEnum {
ENGLISH, HINDI, FRENCH, ALL;
}
ChannelTypeEnumは、さまざまな種類のチャンネルを定義するJavaの列挙型です。Channel.java
package com.scdev.design.iterator;
public class Channel {
private double frequency;
private ChannelTypeEnum TYPE;
public Channel(double freq, ChannelTypeEnum type){
this.frequency=freq;
this.TYPE=type;
}
public double getFrequency() {
return frequency;
}
public ChannelTypeEnum getTYPE() {
return TYPE;
}
@Override
public String toString(){
return "Frequency="+this.frequency+", Type="+this.TYPE;
}
}
Channelは、frequencyとchannel typeという属性を持つシンプルなPOJOクラスです。ChannelCollection.java
package com.scdev.design.iterator;
public interface ChannelCollection {
public void addChannel(Channel c);
public void removeChannel(Channel c);
public ChannelIterator iterator(ChannelTypeEnum type);
}
ChannelCollectionインターフェースは、私たちのコレクションクラスの実装の契約を定義しています。チャンネルを追加・削除するためのメソッドがありますが、チャンネルのリストを返すメソッドはありません。ChannelCollectionには、走査のためのイテレータを返すメソッドがあります。ChannelIteratorインターフェースは、以下のメソッドを定義しています;ChannelIterator.java
package com.scdev.design.iterator;
public interface ChannelIterator {
public boolean hasNext();
public Channel next();
}
今、私たちのベースのインターフェースとコアクラスが準備できましたので、コレクションクラスとイテレータの実装に進みましょう。ChannelCollectionImpl.java
package com.scdev.design.iterator;
import java.util.ArrayList;
import java.util.List;
public class ChannelCollectionImpl implements ChannelCollection {
private List<Channel> channelsList;
public ChannelCollectionImpl() {
channelsList = new ArrayList<>();
}
public void addChannel(Channel c) {
this.channelsList.add(c);
}
public void removeChannel(Channel c) {
this.channelsList.remove(c);
}
@Override
public ChannelIterator iterator(ChannelTypeEnum type) {
return new ChannelIteratorImpl(type, this.channelsList);
}
private class ChannelIteratorImpl implements ChannelIterator {
private ChannelTypeEnum type;
private List<Channel> channels;
private int position;
public ChannelIteratorImpl(ChannelTypeEnum ty,
List<Channel> channelsList) {
this.type = ty;
this.channels = channelsList;
}
@Override
public boolean hasNext() {
while (position < channels.size()) {
Channel c = channels.get(position);
if (c.getTYPE().equals(type) || type.equals(ChannelTypeEnum.ALL)) {
return true;
} else
position++;
}
return false;
}
@Override
public Channel next() {
Channel c = channels.get(position);
position++;
return c;
}
}
}
イテレーターインターフェースの内部クラス実装に注目してください。この実装は他のコレクションに使用することはできません。コレクションクラスも同様のアプローチを採用しており、すべてのコレクションクラスにはイテレーターインターフェースの内部クラス実装があります。私たちのコレクションとイテレーターを使用して、チャネルのコレクションを走査するためのシンプルなイテレーターパターンのテストプログラムを書いてみましょう。IteratorPatternTest.java
package com.scdev.design.iterator;
public class IteratorPatternTest {
public static void main(String[] args) {
ChannelCollection channels = populateChannels();
ChannelIterator baseIterator = channels.iterator(ChannelTypeEnum.ALL);
while (baseIterator.hasNext()) {
Channel c = baseIterator.next();
System.out.println(c.toString());
}
System.out.println("******");
// Channel Type Iterator
ChannelIterator englishIterator = channels.iterator(ChannelTypeEnum.ENGLISH);
while (englishIterator.hasNext()) {
Channel c = englishIterator.next();
System.out.println(c.toString());
}
}
private static ChannelCollection populateChannels() {
ChannelCollection channels = new ChannelCollectionImpl();
channels.addChannel(new Channel(98.5, ChannelTypeEnum.ENGLISH));
channels.addChannel(new Channel(99.5, ChannelTypeEnum.HINDI));
channels.addChannel(new Channel(100.5, ChannelTypeEnum.FRENCH));
channels.addChannel(new Channel(101.5, ChannelTypeEnum.ENGLISH));
channels.addChannel(new Channel(102.5, ChannelTypeEnum.HINDI));
channels.addChannel(new Channel(103.5, ChannelTypeEnum.FRENCH));
channels.addChannel(new Channel(104.5, ChannelTypeEnum.ENGLISH));
channels.addChannel(new Channel(105.5, ChannelTypeEnum.HINDI));
channels.addChannel(new Channel(106.5, ChannelTypeEnum.FRENCH));
return channels;
}
}
上記のプログラムを実行すると、以下の出力が生成されます。
Frequency=98.5, Type=ENGLISH
Frequency=99.5, Type=HINDI
Frequency=100.5, Type=FRENCH
Frequency=101.5, Type=ENGLISH
Frequency=102.5, Type=HINDI
Frequency=103.5, Type=FRENCH
Frequency=104.5, Type=ENGLISH
Frequency=105.5, Type=HINDI
Frequency=106.5, Type=FRENCH
******
Frequency=98.5, Type=ENGLISH
Frequency=101.5, Type=ENGLISH
Frequency=104.5, Type=ENGLISH
イテレーターデザインパターンの重要なポイント
- Iterator pattern is useful when you want to provide a standard way to iterate over a collection and hide the implementation logic from client program.
- The logic for iteration is embedded in the collection itself and it helps client program to iterate over them easily.
JDKにおけるイテレーターデザインパターン
私たちはみんな、Collection frameworkのIteratorがイテレーターパターンの実装のベストな例であることを知っていますが、java.util.ScannerクラスもIteratorインターフェースを実装していることをご存じでしょうか。Java Scannerクラスについては、この投稿を読んで学んでください。イテレーターデザインパターンに関しては以上です。役立つかつ理解しやすいことを願っています。