デザイン パターンの美しさ 65 イテレータ パターン (パート 1): コレクション データを直接トラバースする場合と比較して、イテレータを使用する利点は何ですか?

65 | イテレーター モード (パート 1): コレクション データを直接トラバースする場合と比較して、イテレーターを使用する利点は何ですか?

前回のレッスンでは、状態パターンについて学びました。ステート パターンは、ステート マシンの実装です。ステート マシン クラスの分岐判断ロジックを回避し、ステート マシン クラス コードの複雑さに対処するために、イベント トリガー状態遷移とアクション実行を異なるステート クラスに分割します。

今日は、もう 1 つの行動設計パターンである反復子パターンを学びます。コレクション オブジェクトをトラバースするために使用されます。ただし、多くのプログラミング言語では、イテレータを基本的なクラス ライブラリとして直接提供しています。通常の開発、特にビジネス開発では、直接使用することができ、イテレータを自分で実装することはめったにありません。しかし、それが何であるか、それがなぜなのか、原理を理解することは、これらのツールをよりよく使用するのに役立ちますので、このモードを学ぶことはまだ必要だと思います.

ほとんどのプログラミング言語には、for ループ、foreach ループ、反復子など、コレクションをトラバースするさまざまな方法が用意されています。したがって、今日は、イテレータの原理と実装を説明することに加えて、イテレータを使用してコレクションをトラバースすることの他のトラバーサル方法と比較した利点にも焦点を当てます。

早速、今日から本格的に勉強を始めましょう!

イテレータパターンの原理と実装

イテレーター デザイン パターン (イテレーター デザイン パターン)。カーソル デザイン パターン (カーソル デザイン パターン) とも呼ばれます。

冒頭で述べたように、コレクション オブジェクトをトラバースするために使用されます。ここでいう「コレクション オブジェクト」は「コンテナー」や「集合オブジェクト」とも呼ばれ、実際には配列、リンク リスト、ツリー、グラフ、ジャンプ リストなどのオブジェクトのグループを含むオブジェクトです。反復子モードは、コレクション オブジェクトのトラバーサル操作をコレクション クラスから分離し、それを反復子クラスに配置して、2 つの責任をさらに単一にします。

イテレーターはコンテナーをトラバースするために使用されるため、通常、完全なイテレーター パターンにはコンテナーとコンテナーイテレーターの2 つの部分が含まれます。実装ではなくインターフェイスに基づくプログラミングの目的を達成するために、コンテナーにはコンテナー インターフェイスとコンテナー実装クラスが含まれ、反復子には反復子インターフェイスと反復子実装クラスが含まれます。イテレータ パターンについては、簡単なクラス図を描いたので、まず全体の印象をつかんでください。

ここに画像の説明を挿入

次に、例を使用して、反復子を実装する方法について具体的に説明しましょう。

冒頭で述べたように、ほとんどのプログラミング言語はコンテナーをトラバースするためのイテレーター クラスを提供しています. 私たちの日常の開発では、それらを直接使用することができます. イテレーターを最初から書くことはほとんど不可能です. ただし、イテレータの実装原理を説明するには、新しいプログラミング言語の基本クラス ライブラリには線形コンテナに対応するイテレータが用意されておらず、ゼロから開発する必要があると仮定します。では、一緒にやり方を見ていきましょう。

線形データ構造には配列と連結リストが含まれることがわかっています. ほとんどのプログラミング言語には、開発で直接使用できるこれら 2 つのデータ構造をカプセル化するための対応するクラスがあります。この新しいプログラミング言語では、これら 2 つのデータ構造が ArrayList と LinkedList の 2 つのクラスにそれぞれ対応しているとします。さらに、開発者が実装ではなくインターフェイスに基づいてプログラミングでき、記述されたコードが 2 つのデータ ストレージ構造を柔軟に切り替えることができるように、2 つのクラスからパブリック インターフェイスを抽象化し、それを List インターフェイスとして定義しました。

ここで、ArrayList と LinkedList の 2 つの線形コンテナーに対応するイテレーターを設計して実装します。前に示したイテレーター パターンのクラス図に従って、イテレーター インターフェイス Iterator を定義し、特定のイテレーター実装クラス ArrayIterator と ListIterator を 2 つのコンテナーに定義します。

最初に Iterator インターフェースの定義を見てみましょう。具体的なコードは次のとおりです。

// 接口定义方式一
public interface Iterator<E> {
  boolean hasNext();
  void next();
  E currentItem();
}

// 接口定义方式二
public interface Iterator<E> {
  boolean hasNext();
  E next();
}

Iterator インターフェイスを定義するには、2 つの方法があります。

最初の定義では、next() 関数を使用してカーソルを 1 つ要素を戻し、currentItem() 関数を使用して現在のカーソルが指す要素を返します。2 番目の定義では、現在の要素を返す操作と 1 ビット後方にシフトする操作の 2 つが、同じ関数 next() で完了する必要があります。

最初の定義はより柔軟です。たとえば、 currentItem() を複数回呼び出して、カーソルを移動せずに現在の要素をクエリできます。したがって、次の実装では、最初のインターフェイス定義方法を選択します。

ここで、以下に示すように、ArrayIterator のコード実装をもう一度見てみましょう。コードの実装は非常に単純で、多くの説明は必要ありません。私が提供したデモを組み合わせて、自分で理解することができます。

public class ArrayIterator<E> implements Iterator<E> {
  private int cursor;
  private ArrayList<E> arrayList;

  public ArrayIterator(ArrayList<E> arrayList) {
    this.cursor = 0;
    this.arrayList = arrayList;
  }

  @Override
  public boolean hasNext() {
    return cursor != arrayList.size(); //注意这里,cursor在指向最后一个元素的时候,hasNext()仍旧返回true。
  }

  @Override
  public void next() {
    cursor++;
  }

  @Override
  public E currentItem() {
    if (cursor >= arrayList.size()) {
      throw new NoSuchElementException();
    }
    return arrayList.get(cursor);
  }
}

public class Demo {
  public static void main(String[] args) {
    ArrayList<String> names = new ArrayList<>();
    names.add("xzg");
    names.add("wang");
    names.add("zheng");

    Iterator<String> iterator = new ArrayIterator(names);
    while (iterator.hasNext()) {
      System.out.println(iterator.currentItem());
      iterator.next();
    }
  }
}

上記のコード実装では、トラバースするコンテナー オブジェクトを、コンストラクターを介してイテレーター クラスに渡す必要があります。実際、イテレータの作成の詳細をカプセル化するために、コンテナに iterator() メソッドを定義して、対応するイテレータを作成できます。実装ではなくインターフェイスに基づいてプログラミングを実装するには、List インターフェイスでこのメソッドを定義する必要もあります。具体的なコードの実装と使用例は次のとおりです。

public interface List<E> {
  Iterator iterator();
  //...省略其他接口函数...
}

public class ArrayList<E> implements List<E> {
  //...
  public Iterator iterator() {
    return new ArrayIterator(this);
  }
  //...省略其他代码
}

public class Demo {
  public static void main(String[] args) {
    List<String> names = new ArrayList<>();
    names.add("xzg");
    names.add("wang");
    names.add("zheng");

    Iterator<String> iterator = names.iterator();
    while (iterator.hasNext()) {
      System.out.println(iterator.currentItem());
      iterator.next();
    }
  }
}

LinkedIterator の場合、そのコード構造は ArrayIterator とまったく同じであるため、ここでは具体的なコードの実装は示しません。ArrayIterator を参照して自分で記述できます。

先ほどの例と合わせて、イテレータの設計思想をまとめてみましょう。3 つの文にまとめると、イテレータは、hasNext()、currentItem()、および next() の 3 つの基本的なメソッドを定義する必要があります。トラバースされるコンテナー オブジェクトは、依存性注入によってイテレーター クラスに渡されます。コンテナは、 iterator() メソッドを通じてイテレータを作成します。

ここで、以下のようなクラス図を描きました。実際、これは上のクラス図を改良したものであり、一緒に見ることができます。

ここに画像の説明を挿入

イテレータ パターンの利点

イテレータの原理とコードの実装が終了しました。次に、反復子を使用してコレクションをトラバースする利点を見てみましょう。

一般に、コレクション データをトラバースするには、for ループ、foreach ループ、および iterator iterator の 3 つの方法があります。これら 3 つの方法については、Java 言語を例として使用します。具体的なコードは次のとおりです。

List<String> names = new ArrayList<>();
names.add("xzg");
names.add("wang");
names.add("zheng");

// 第一种遍历方式:for循环
for (int i = 0; i < names.size(); i++) {
  System.out.print(names.get(i) + ",");
}

// 第二种遍历方式:foreach循环
for (String name : names) {
  System.out.print(name + ",")
}

// 第三种遍历方式:迭代器遍历
Iterator<String> iterator = names.iterator();
while (iterator.hasNext()) {
  System.out.print(iterator.next() + ",");//Java中的迭代器接口是第二种定义方式,next()既移动游标又返回数据
}

実際、foreach ループは単なるシンタックス シュガーであり、最下層は反復子に基づいて実装されています。つまり、上記のコードの 2 番目のトラバーサル メソッド (foreach ループ コード) の基になる実装は、3 番目のトラバーサル メソッド (イテレータ トラバーサル コード) です。これら 2 つのトラバーサル メソッドは、同じトラバーサル メソッド、つまりイテレータ トラバーサル メソッドと見なすことができます。

上記のコードから、for ループ トラバーサル メソッドはイテレータ トラバーサル メソッドよりも簡潔に見えます。では、なぜイテレータを使用してコンテナをトラバースするのでしょうか? コンテナに対応するイテレータを設計する理由 これには 3 つの理由があります。

まず、配列や連結リストなどのデータ構造の場合、走査方法は比較的単純であり、for ループを使用して直接走査するだけで十分です。ただし、複雑なデータ構造 (ツリーやグラフなど) の場合は、さまざまな複雑な走査方法があります。たとえば、ツリーには前後の順序、レイヤーごとのトラバーサルがあり、グラフには深さ優先、幅優先のトラバーサルなどがあります。これらのトラバーサル アルゴリズムをクライアント コードで実装すると、必然的に開発コストが増加し、書き間違えやすくなります。トラバーサル ロジックのこの部分をコンテナ クラスに記述すると、コンテナ クラスのコードも複雑になります。

前に何度も述べたように、複雑さに対処する方法は分割することです。トラバーサル操作をイテレータ クラスに分割できます。たとえば、グラフ トラバーサルの場合、DFSIerator と BFSIterator という 2 つの反復子クラスを定義し、それぞれに深さ優先トラバーサルと幅優先トラバーサルを実装させることができます。

次に、カーソルが指す現在位置などの情報がイテレータ クラスに格納され、各イテレータはカーソル情報を排他的に使用します。このようにして、複数の異なるイテレータを作成して、互いに影響を与えることなく同じコンテナを同時にトラバースできます。

最後に、コンテナーとイテレーターの両方が抽象インターフェースを提供します。これは、開発中に特定の実装ではなく、インターフェースに基づいてプログラムするのに便利です。新しいトラバーサル アルゴリズムに切り替える必要がある場合、たとえば、リンクされたリストを前から後ろへトラバースすることから、リンクされたリストを後ろから前へトラバースするように切り替える場合、クライアント コードはイテレーター クラスを LinkedIterator から ReversedLinkedIterator に切り替えるだけで済みます。他のコードを変更する必要はありません。さらに、新しいトラバーサル アルゴリズムを追加するには、新しい反復子クラスを拡張するだけで済みます。これは、開始と終了の原則に沿ったものです。

キーレビュー

では、本日の内容は以上です。集中する必要があることをまとめて一緒に確認しましょう。

イテレータ モード。カーソル モードとも呼ばれます。コレクション オブジェクトをトラバースするために使用されます。ここで言う「コレクションオブジェクト」とは、「コンテナ」や「集合オブジェクト」とも呼ばれ、実際には配列、連結リスト、ツリー、グラフ、ジャンプリストなどのオブジェクトの集まりを含むオブジェクトです。

通常、完全なイテレーター パターンには、コンテナーとコンテナー イテレーターの 2 つの部分が含まれます。実装ではなくインターフェイスに基づくプログラミングの目的を達成するために、コンテナにはコンテナ インターフェイスとコンテナ実装クラスが含まれ、イテレータにはイテレータ インターフェイスとイテレータ実装クラスが含まれます。イテレーターを作成するには、コンテナーで iterator() メソッドを定義する必要があります。hasNext()、currentItem()、および next() の 3 つの基本的なメソッドを iterator インターフェイスで定義する必要があります。コンテナー オブジェクトは、依存性注入を介してイテレーター クラスに渡されます。

コレクションをトラバースするには、通常、for ループ、foreach ループ、イテレータ トラバーサルの 3 つの方法があります。後者の 2 つは基本的に 1 種類であり、どちらもイテレータ トラバーサルと見なすことができます。for ループ トラバーサルと比較して、反復子を使用してトラバースすると、次の 3 つの利点があります。

  • The iterator mode encapsulates the complex data structure inside the collection. 開発者はトラバース方法を知る必要はなく、コンテナーによって提供される反復子を使用するだけです。
  • イテレータ モードは、コレクション オブジェクトのトラバーサル操作をコレクション クラスから分離し、それをイテレータ クラスに入れ、2 つの責任をより単一にします。
  • イテレータ パターンを使用すると、新しいトラバーサル アルゴリズムを簡単に追加でき、オープン/クローズドの原則にさらに沿ったものになります。また、イテレータはすべて同じインターフェースから実装されているため、プログラムの実装よりもインターフェースを基にした開発時にイテレータを置き換える方が簡単です。

クラスディスカッション

  1. Java では、反復子の使用中にコンテナー内の要素を削除すると、反復子によってエラーが報告されます。この問題を解決するには?
  2. プログラミング言語の基本クラス ライブラリによって提供されるコレクション オブジェクトの反復子に加えて、実際には、first()、last()、previous()、および MySQL によって提供されるその他のメソッドなど、反復子の他のアプリケーション シナリオがあります。 ResultSet クラスです。イテレータと見なすこともできますが、コードの実装を分析できますか?

メッセージを残して、あなたの考えを私と共有してください。何かを得た場合は、この記事を友達と共有してください。

おすすめ

転載: blog.csdn.net/fegus/article/details/130519291