blog

デザインパターン学習ノート:Iteratorパターン

ソフトウェア開発では、複数のメンバーオブジェクトを格納できるクラスがあります。これらのクラスは通常、集約クラスと呼ばれ、対応するオブジェクトは集約オブジェクトと呼ばれます。集約オブジェクトには2つの責...

Dec 3, 2020 · 11 min. read
シェア

概要

はじめに

ソフトウェア開発では、複数のメンバオブジェクトを格納できるクラスがあります。これらのクラスは通常、集約クラスと呼ばれ、対応するオブジェクトは集約オブジェクトと呼ばれます。集約オブジェクトには2つの責務があり、1つはデータを格納すること、もう1つはデータをトラバースすることです。前者は集約オブジェクトの基本的な責務ですが、後者は分離するだけでなく変更することもできます。したがって、データをトラバースする振る舞いを集約オブジェクトから分離し、「イテレータ」と呼ばれるオブジェクトにカプセル化し、イテレータで集約オブジェクト内部のデータをトラバースする振る舞いを提供することができます。イテレータは、集約オブジェクトの内部でデータを走査する振る舞いを提供します。

定義

Iteratorパターン:オブジェクトの内部表現を公開することなく、オブジェクトにアクセスする方法を提供します。

Iterator パターンはオブジェクトの振る舞いのパターンです。

構造図

役割

  • Iterator: 要素にアクセスしてトラバースするためのインターフェイスを定義し、first, next, hasNext, currentItemなどのデータ要素をトラバースするためのメソッドを宣言します。
  • ConcreteIterator: 抽象的なイテレータを実装し、集約オブジェクトの走査を完了します。一方、具体的なイテレータでは、集約オブジェクトの現在の位置を記録するカーソルを介して、通常、カーソルは非負の整数です。
  • Aggregate: 要素オブジェクトの保存と管理に使用され、イテレータオブジェクトを作成する createIterator メソッドを宣言し、抽象イテレータのファクトリとして機能します。
  • ConcreteAggregate: 抽象集約クラスの createIterator メソッドを実装し、具象イテレータのインスタンスを返します。

典型的な実装

ステップ

  • 抽象イテレータの定義:一般的にはインターフェース、具象イテレータのメソッドの宣言
  • 抽象集約クラスを定義します: 通常は、集約要素を管理し、抽象イテレータを作成するためのメソッドを含むインターフェイスです。
  • 具象イテレータクラスを定義します。抽象イテレータを実装するメソッドで、通常は具象集約クラスからのコレクションへの参照と、要素の位置を整数で表すカーソルを含みます。

抽象イテレータ

interface Iterator
{
 String first();
 String next();
 boolean hasNext();
 String currentItem();
}

抽象集約クラス

interface Aggregate
{
 Iterator createIterator();
 void add(String s);
}

イテレータは createIterator で作成し、要素を追加するには add を使用します。

特定の集約クラス

class ConcreteAggregate implements Aggregate
{
 List<String> list = new ArrayList<>();
 @Override
 public Iterator createIterator()
 {
 return new ConcreteIterator(list);
 }
 @Override
 public void add(String s)
 {
 list.add(s);
 }
}

特定のイテレータ

class ConcreteIterator implements Iterator
{
 private int cursor;
 private List<String> list;
 public ConcreteIterator(List<String> list)
 {
 this.list = list;
 this.cursor = -1;
 }
 @Override
 public String first()
 {
 return list.size() > 0 ?
 list.get(cursor = 0) :
 null;
 }
 @Override
 public String next()
 {
 return list.get(
 cursor + 1 < list.size() ? ++cursor : cursor
 );
 }
 @Override
 public boolean hasNext()
 {
 return cursor+1 < list.size();
 }
 @Override
 public String currentItem()
 {
 return list.get(cursor);
 }
}

具象イテレータには、現在アクセスされている位置を追跡するためのカーソルが含まれます。カーソルはコンストラクタのメソッドで0ではなく-1に初期化され、nextが最初に使用されたときに最初の要素にアクセスできるようにします。

クライアント

public static void main(String[] args) 
{
 Aggregate aggregate = new ConcreteAggregate();
 aggregate.add("111");
 aggregate.add("222");
 aggregate.add("jksdfjksdjkfk");
 aggregate.add("m,xcvm,xcm,v");
 Iterator iterator = aggregate.createIterator();
 while(iterator.hasNext())
 {
 System.out.println(iterator.next());
 }
}

抽象アグリゲートクラスと抽象イテレータのクライアントプログラミングは、イテレータを作成するアグリゲートオブジェクトを介して、HaxNext判定の最初の使用、要素を取得するためにnextの使用が続きます。

インスタンス

イテレータ・パターンを使用して、顧客データと製品データをトラバースするシステムを設計します。

この例も実は上の例と似ていますが、逆イテレータ・メソッドです。 また、実際の環境に近づけるため、抽象イテレータと集約クラスはジェネリクスを使って設計されています:

  • 抽象イテレータ: イテレータ
  • 抽象イテレータ: イテレータ<T>
  • 抽象集約クラス:AbstarctList<T>
  • 特定の集約クラス: ObjectList<T>
  • 特定のイテレータ:ObjectIterator<T>

まず、抽象イテレータを設計します:

interface Iterator<T>
{
 T next();
 boolean hasNext();
 String nextName() throws UnsupportedOperationException;
 boolean hasNextName() throws UnsupportedOperationException;
 void setProduct();
}

setProduct() は、集約要素のタイプを Product に設定します。

次に、抽象集約クラスの設計です:

interface AbstractList<T>
{
 Iterator<T> iterator();
 Iterator<T> reversedIterator();
 void add(T s);
}

逆イテレータの実装を追加。

次に、特定のアグリゲーション・クラスの設計です:

class ObjectList<T> implements AbstractList<T>
{
 List<T> list = new ArrayList<>();
 @Override
 public Iterator<T> iterator()
 {
 return new ObjectIterator<T>(list,false);
 }
 @Override
 public void add(T s)
 {
 list.add(s);
 }
 @Override
 public Iterator<T> reversedIterator()
 {
 return new ObjectIterator<T>(list,true);
 }
}

また、集約された要素を格納するための内部リストもあります。iterator は順方向イテレータを返し、コンストラクタ・メソッド内のブール値は逆方向イテレータであるかどうかを示します。

最後に、具象反復子クラスです:

class ObjectIterator<T> implements Iterator<T>
{
 private int cursor;
 private List<T> list;
 private boolean reversed;
 private boolean isProduct = false;
 public ObjectIterator(List<T> list,boolean reversed)
 {
 this.list = list;
 this.reversed = reversed;
 this.cursor = (reversed ? list.size() : -1);
 }
 @Override
 public void setProduct()
 {
 isProduct = true;
 }
 @Override
 public T next()
 {
 return list.get(
 reversed ? 
 ( cursor - 1 >= 0 ? --cursor : cursor ) :
 ( cursor + 1 < list.size() ? ++cursor : cursor )
 );
 }
 @Override
 public boolean hasNext()
 {
 return reversed ?
 cursor-1 >= 0 :
 cursor+1 < list.size();
 }
 @Override
 public String nextName() throws UnsupportedOperationException
 {
 if(isProduct)
 throw new UnsupportedOperationException("この操作は商品イテレータではサポートされていない");
 return ((Customer)next()).getName();
 }
 @Override
 public boolean hasNextName() throws UnsupportedOperationException
 {
 if(isProduct)
 throw new UnsupportedOperationException("この操作は商品イテレータではサポートされていない");
 return hasNext();
 }
}

コンストラクタは集約要素と逆イテレータであるかどうかを示すブール値 reversed を初期化し、カーソルは reversed に応じて -1 または list.size() に設定されます。nextメソッドとhasNextメソッドでは、逆イテレータかどうかを判断し、対応する結果を返す必要があります。nextName と hasNextName については、これらのメソッドは Customer クラスに対してのみ動作するため、Product クラスでは例外がスローされます。

その他

class Product
{
 private String id;
 private int num;
 public Product(){}
 public Product(String id,int num) {
 this.id = id;
 this.num = num;
 }
 public String getId() {
 return this.id;
 }
 public int getNum() {
 return this.num;
 }
 @Override
 public String toString()
 {
 return "製品ID:"+id+"\t製品数:"+num;
 }
}
class Customer
{
 private String id;
 private String name;
 public Customer(String id,String name)
 {
 this.id = id;
 this.name = name;
 }
 public String getId() {
 return this.id;
 }
 public String getName() {
 return this.name;
 }
 @Override
 public String toString()
 {
 return "顧客ID:"+id+"\t顧客名:"+name;
 }
}

テストクラス:

public static void main(String[] args) 
{
 Customer customer1 = new Customer("id1","name1");
 Customer customer2 = new Customer("id2","name2");
 Customer customer3 = new Customer("id3","name3");
 AbstractList<Customer> customerList = new ObjectList<>();
 customerList.add(customer1);
 customerList.add(customer2);
 customerList.add(customer3);
 Iterator<Customer> customerIterator = customerList.iterator();
 while(customerIterator.hasNext())
 System.out.println(customerIterator.next());
 customerIterator = customerList.reversedIterator();
 while(customerIterator.hasNext())
 System.out.println(customerIterator.next());
 System.out.println();
 customerIterator = customerList.iterator();
 while(customerIterator.hasNextName())
 System.out.println(customerIterator.nextName());
 customerIterator = customerList.reversedIterator();
 while(customerIterator.hasNextName())
 System.out.println(customerIterator.nextName());
 System.out.println();
 
 Product product1 = new Product("product id 1",1);
 Product product2 = new Product("product id 2",2);
 Product product3 = new Product("product id 3",3);
 AbstractList<Product> productList = new ObjectList<>();
 productList.add(product1);
 productList.add(product2);
 productList.add(product3);
 Iterator<Product> productIterator = productList.iterator();
 while(productIterator.hasNext())
 System.out.println(productIterator.next());
 productIterator = productList.reversedIterator();
 while(productIterator.hasNext())
 System.out.println(productIterator.next());
 System.out.println();
 try
 {
 productIterator = productList.iterator();
 productIterator.setProduct();
 while(productIterator.hasNextName())
 System.out.println(productIterator.nextName());
 }
 catch(Exception e)
 {
 e.printStackTrace();
 }
}

まず、3つのCustomerが作成され、customerListに追加され、forwardイテレータはcustomerListのイテレータから取得され、forwardイテレータはreversedIteratorから取得され、2つのトラバーサルイテレータは同じステートメントを使用してトラバースすることができます:

while(customerIterator.hasNext())
	System.out.println(customerIterator.next());

Productの場合、hasNextNameとnextNameは例外をスローすると宣言されているので、テスト出力は次のようになります:

内部クラスの実装

上記の例では、具体的な集約クラスと具体的な反復子との間に関連があり、具体的な集約オブジェクトへの参照を維持する必要があることを示しています。 関連を使用するだけでなく、反復子は、JDK の AbstractList のように、集約クラスの内部クラスとして設計することもできます:

public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
	//...
	private class Itr implements Iterator<E> {
		//...
	}
	//...
}

同じようなアプローチで上の例を再設計してください:

class ObjectList<T> implements AbstractList<T>
{
 List<T> list = new ArrayList<>();
 @Override
 public Iterator<T> iterator()
 {
 return new ObjectIterator(false);
 }
 @Override
 public void add(T s)
 {
 list.add(s);
 }
 @Override
 public Iterator<T> reversedIterator()
 {
 return new ObjectIterator(true);
 }
 private class ObjectIterator implements Iterator<T>
 {
 private int cursor;
 private boolean reversed;
 private boolean isProduct = false;
 public ObjectIterator(boolean reversed)
 {
 this.reversed = reversed;
 this.cursor = (reversed ? list.size() : -1);
 }
 //...
 }
}

JDK

Iterator

JDKのCollectionメソッドの抜粋を以下に示します:

public interface Collection<E> extends Iterable<E> {
	//...
	Iterator<E> iterator();
	//...
}

このメソッドは、集約されたクラスの要素を反復処理するためのイテレータを返すために使用されます:

public interface Iterator<E> {
	boolean hasNext();
	E next();
	default void remove() {
		//...
	}
	default void forEachRemaining(Consumer<? super E> action) {
		//...
	}
}

その中でも

  • hasNext: 集約オブジェクトの次の要素が存在するかどうかを判断するために使用されます。
  • next: カーソルを次の要素に移動し、カーソルが横切った要素への参照を返す、つまり次の要素を取得します。
  • remove: 最後の next が返す要素を削除します。
  • forEachRemaining:このような10の要素のコレクションとして、操作の残りの要素に使用され、イテレータの使用は、最初の5をトラバース、このメソッドの使用は、残りの要素をトラバースされます、つまり、後者の5

Javaのイテレータの原理を図に示します:

次にnextが呼ばれると、カーソルは移動を続け、位置1から位置2に移動し、移動した要素、つまり要素2を返します。つまり、この時点でremoveが呼ばれると、要素2が削除されます。

つまり、removeを呼び出す前に少なくとも一度はnextを呼び出す必要があり、nextを呼び出さない場合は例外がスローされます:

ListIterator

コレクションインターフェイスのイテレータの継承に加えて、JDKのリストインターフェイスだけでなく、具体的にListIterator型のイテレータを作成するために使用されるListIteratorを追加します。コレクションの走査のためにIteratorされているが、このイテレータは、前方走査にのみ使用することができ、ListIteratorの出現は、それがhasPreviousとpreviousメソッドを提供するため、逆方向走査の問題を解決することができます。以下に例を示します:

public static void main(String[] args) {
 List<String> s = new ArrayList<>();
 s.add("1111");
 s.add("2222");
 s.add("3333");
 ListIterator<String> it = s.listIterator();
 while(it.hasNext())
 System.out.println(it.next());
 System.out.println();
 while(it.hasPrevious())
 System.out.println(it.previous());
}

完全な逆トラバーサルを実装するには、まずカーソルを最後まで移動させる必要があります。つまり、逆トラバーサルのためにpreviousを呼び出すには、最後までnextを呼び出し続ける必要があります。

主な利点

  • 複数のトラバーサルメソッド:集約されたオブジェクトをトラバースするさまざまな方法をサポートし、同じ集約オブジェクト内のトラバーサルメソッドの様々な定義することができ、単にトラバーサルアルゴリズムを変更することができます別のアグリゲータで元のイテレータを置き換えます。
  • 集計クラスの簡素化:元の集計オブジェクトは、独自のデータ・トラバーサル・メソッドを提供する必要がなくなりました。
  • OCPを満たす:抽象化レイヤーの導入により、ソースコードを変更することなく、新しいアグリゲータ・クラスやイテレータ・クラスを追加することが容易になります。

主な欠点

  • 複雑さの増加:イテレータパターンは、データの格納とトラバースの責任を分離します。新しい集約クラスを追加するには、対応する新しいイテレータクラスを追加する必要があり、複雑さが増加します。
  • 抽象的なイテレータは、設計がより困難である:将来の拡張を考慮に入れて、抽象的なイテレータの設計は非常に困難なことができるようなJDKの組み込みイテレータのイテレータは、逆トラバーサルで達成することはできません、抽象的なイテレータの包括的な検討の設計は簡単な作業ではありません。

シナリオ

  • 内部表現を公開せずに集約オブジェクトのコンテンツにアクセスします。集約オブジェクトへのアクセスを内部データストアから分離することで、内部実装の詳細を知ることなく集約オブジェクトにアクセスできます。
  • 集約オブジェクトに対して複数のトラバーサルが必要
  • 異なる集約構造をトラバースするための統一されたインターフェイスを提供し、インターフェイスの実装クラスは異なる集約構造に対して異なるトラバースメソッドを提供し、クライアントは一貫してインターフェイスを操作することができます。

Read next

javaの見通し、万以上のjavaエンジニアの月収になる それがいかに難しいか

Java言語は、開発の20年以上を経験している、C、C + +言語では、状況の川や山の大半を占めているどこからともなく出てきた、PHP、Python、Rubyや他の動的言語にも臆病さを示していないに直面して、血まみれの道を殺した、まだ最も人気のあるプログラミング言語ですが、誰も、あなたはJavaが新星とパラダイムのオブジェクト指向言語であると言うことができます。 これは、Javaは後発とオブジェクト指向言語のモデルであると言うことができます。

Dec 3, 2020 · 3 min read