イテレーターをデザインパターンにする理由


9

他の同様の構成と比較してIteratorが特別になるのは何だろうと思っていました。そのため、Gang of Fourがデザインパターンとしてリストされています。

イテレーターは、ポリモーフィズム(共通のインターフェースを持つコレクションの階層)と懸念の分離(コレクションの反復は、データの構造化方法から独立している必要があります)に基づいています。

しかし、コレクションの階層を、たとえば数学オブジェクト(整数、浮動小数点数、複素数、行列など)の階層と反復子を、これらのオブジェクトの関連するいくつかの操作を表すクラス(例えば、指数関数)で置き換えるとどうなるでしょうか。クラス図も同じです。

同じように機能する、Writer、Painter、Encoderなどのより多くの類似の例、おそらくより優れた例を見つけることができます。しかし、これらがデザインパターンと呼ばれるのを聞いたことがありません。

では、イテレータが特別な理由は何ですか?

コレクション内の現在の位置を格納するために変更可能な状態を必要とするため、より複雑になるのは事実ですか?しかし、その場合、変更可能な状態は通常望ましいとは見なされません。


私のポイントを明確にするために、より詳細な例を挙げましょう。

ここに私たちの設計問題があります:

クラスの階層と、これらのクラスのオブジェクトに対して定義された操作があるとします。この操作のインターフェースは各クラスで同じですが、実装は完全に異なる場合があります。同じオブジェクトに複数の操作を適用すること、たとえば異なるパラメーターを使用することが理にかなっていることも想定されています。

これが私たちの設計問題(実際には反復子パターンの一般化)の賢明な解決策です:

懸念を分離するために、操作の実装を関数として元のクラス階層(オペランドオブジェクト)に追加しないでください。同じオペランドに操作を複数回適用する必要があるため、関数だけでなく、オペランドへの参照を保持するオブジェクトで表す必要があります。したがって、オペランドオブジェクトは、演算を表すオブジェクトを返す関数を提供する必要があります。このオブジェクトは、実際の操作を実行する関数を提供します。

例:

MathObject派生クラスMyIntegerとを備えた基本クラスまたはインターフェイス(愚かな名前です。たぶん誰かがより良いアイデアを持っています。)がありMyMatrixます。それぞれについて、正方形、立方体などの計算を可能にMathObjectする操作Powerを定義する必要があります。だから私たちは(Javaで)書くことができました:

MathObject i = new MyInteger(5);
Power powerOfFive = i.getPower();
MyInteger square = powerOfFive.calculate(2); // should return 25
MyInteger cube = powerOfFive.calculate(3); // should return 125

5
「クラス図は同じだろう」-だから何?設計パターンはクラス図ではありません。これは、繰り返し発生する問題に対するある種のソリューションの抽象化です。
Doc Brown、

@DocBrown:そうですが、数学的な操作、オブジェクトのファイルへの書き込み、グラフィカルな出力、または反復的なデータのような繰り返し発生する問題のエンコードではありませんか?
フランクパファー2016年

デザインパターンの選択は主観的です(つまり、「デザイナー」、またはデザインを判断する人々の目で)。設計パターンの命名は、ドメインにとらわれないようにすることを目的としています(ドメイン固有であると考えることに気を取られないようにするため)。ただ私の意見ですが、引用するリファレンスはありません。
rwong 2016年

@FrankPufferオブジェクトをファイルに書き込むための一般的なソリューションの概要を説明する場合、ソリューションを書き留めておくと、オブジェクト書き込みパターンと呼ぶことができます。
Brandin

3
あなたはこれを考えすぎています。デザインパターンは、一般的なコンピューティングの問題に対するよく知られたソリューションであり、それがすべてです。提供する利点を認識して適用できる場合は、パターンを使用します。
Robert Harvey

回答:


9

GoF本のほとんどのパターンには、次の共通点があります。

  • オブジェクト指向の手段を使用して、基本的な設計問題を解決する
  • 人々はしばしば、ドメインまたはビジネスから独立して、任意のプログラムでこれらの種類の問題に直面します
  • それらは、多くの場合それをより確実にすることにより、コードをより再利用可能にするためのレシピです
  • 彼らはこれらの問題の標準的な解決策を提示します

これらのパターンによって解決される問題は非常に基本的であり、多くの開発者は、主に欠落しているプログラミング言語機能の回避策としてそれらを理解しています。現在の機能)。

イテレーターパターンはこの説明によく適合します。特定のドメインとは無関係に、非常に頻繁に発生する基本的な問題を解決します。自分で書いたように、「懸念の分離」の良い例です。ご存じのように、直接イテレータのサポートは、今日の多くの現代的なプログラミング言語に見られるものです。

これを、選択した問題と比較します。

  • ファイルへの書き込み-それは私見であり、単に「基本」では不十分です。これは非常に具体的な問題です。また、優れた標準的な解決策もありません。ファイルへの書き込み方法にはさまざまなアプローチがあり、明確な「ベストプラクティス」はありません。
  • ペインター、エンコーダー:あなたがそれを念頭に置いているものは何でも、それらの問題は私にとってはあまり基本的ではなく、ドメインに依存していません。
  • さまざまな種類のオブジェクトで「パワー」関数を使用できるようにする:一見、それはパターンに値する価値があるかもしれませんが、提案されたソリューションは私を納得させません-パワー関数をイテレータパターン。エンジニアリング計算を使用して多くのコードを実装しましたが、パワー関数オブジェクトと同様のアプローチが役に立った状況を思い出せません(ただし、イテレータは日常的に対処しなければならないものです)。

さらに、ストラテジーパターンまたはコマンドパターンのアプリケーションとして解釈できないパワー関数の例には何も見られません。つまり、これらの基本的な部分はGoFブックにすでに含まれています。より優れたソリューションには、演算子のオーバーロードまたは拡張メソッドのいずれかが含まれている可能性がありますが、これらは言語機能の影響を受けるため、「Gang」で使用される「OOの意味」では提供できませんでした。


The problems solved by these patterns are so basic that many developers think their main purpose is to be workarounds for missing programming language features-皮肉なことに、ソフトウェア開発者は、20年前のソフトウェア設計パターンを日常的に使用しながら、最先端のコードを作成していると信じています。
Robert Harvey

@RobertHarvey:多くの開発者がGoFによって提案された「OOの方法」で今日の反復子パターンを実装するとは思いません。それらは、通常、言語または標準のlibによって提供される方法でそれを実装します(たとえば、C IEnumerableおよびを使用してC#でyield)。しかし、他のGoFパターンについては、あなたが書いたことがおそらく本当かもしれません。
Doc Brown

1
workarounds for missing programming language features: blog.plover.com/prog/johnson.htmlに
jrw32982はMonica

8

ギャングオブフォーは、クリストファーアレクサンダーのパターン定義を引用しています。

各パターンは、私たちの環境で繰り返し発生する問題を説明し、その問題の解決策の中核を説明します[…]

イテレータによって解決される問題は何ですか?

意図:基礎となる表現を公開せずに、集計オブジェクトの要素に順次アクセスする方法を提供します。

適用範囲:イテレーターパターンを使用

  • 内部表現を公開せずに集約オブジェクトのコンテンツにアクセスする
  • 集合オブジェクトの複数の走査をサポートする
  • 異なる集約構造を横断するための統一されたインターフェースを提供する(つまり、多態的な反復をサポートする)。

したがって、イテレータパターンは定義上、コレクションに固有のドメインであると主張できます。そして、それは完全に大丈夫です。インタプリタパターンのような他のパターンはドメイン固有の言語にドメイン固有であり、ファクトリパターンはオブジェクト作成にドメイン固有です、…。もちろん、これは「ドメイン固有」のかなりばかげた理解です。再発する問題とソリューションのペアである限り、これをパターンと呼ぶことがあります。

そして、Iteratorパターンが存在するのは良いことです。使わないと悪いことが起こります。私のお気に入りの反例はPerlです。ここで、各コレクション(配列またはハッシュ)には、コレクションの一部としてイテレーター状態が含まれています。なぜこれが悪いのですか?while-eachループを使用して、ハッシュを簡単に反復できます。

while (my ($key, $value) = each %$hash) {
  say "$key => $value";
}

しかし、ループ本体で関数を呼び出すとどうなりますか?

while (my ($key, $value) = each %$hash) {
  do_something_with($key, $value, $hash);
}

この関数は、次の場合を除いて、必要な処理をほとんど実行できます。

  • ハッシュエントリを追加または削除します。これは、反復の順序が予期せず変更されるためです(C ++では、これらはイテレータを無効にします)。
  • コピーを行わずに同じハッシュテーブルを反復処理します。これは、同じ反復状態を消費するためです。おっとっと。

呼び出された関数がイテレータを使用する場合、ループの動作は未定義になります。それが問題です。イテレータパターンには解決策があります。すべての反復状態を、反復ごとに作成される個別のオブジェクトに配置します。

はい、もちろん、イテレータパターンは他のパターンに関連しています。たとえば、イテレータはどのようにインスタンス化されますか?Javaでは、ジェネリックIterable<T>Iterator<T>インターフェースがあります。具象反復可能オブジェクトArrayList<T>は特定の種類の反復子を作成しますが、a HashSet<T>は完全に異なる反復子タイプを提供する場合があります。これは、抽象的なファクトリパターンを非常に思い出させます。ここで、Iterable<T>は抽象ファクトリであり、Iteratorは製品です。

ポリモーフィック反復子は、戦略パターンの例としても解釈できます。たとえば、ツリーはさまざまな種類のイテレータを提供する場合があります(事前注文、注文順、注文後など)。外部的には、これらはすべてイテレーターインターフェースを共有し、いくつかのシーケンスで要素を生成します。クライアントコードは、特定のツリートラバーサルアルゴリズムではなく、イテレータインターフェースに依存する必要があるだけです。

パターンは互いに独立して存在するわけではありません。一部のパターンは同じ問題に対する異なるソリューションであり、一部のパターンは異なるソリューションを同じコンテキストで説明しています。一部のパターンは別のパターンを意味します。また、デザインパターンブックの最後のページをめくっても、パターンスペースは閉じていません(前の質問「ギャングオブフォー」で「パターンスペース」を徹底的に調べましたか?も参照してください)。「デザインパターン」の本で説明されているパターンは、非常に柔軟で幅広いものであり、無限のバリエーションを受け入れるものであり、存在する唯一のパターンではありません。

リストする概念(書き込み、ペイント、エンコーディング)は、問題とソリューションの組み合わせを説明しないため、パターンではありません。「データを書き込む必要がある」や「データをエンコードする必要がある」などのタスクは、実際には設計上の問題ではなく、解決策は含まれていません。「わかっている、Writerクラスを作成する」だけで構成される「ソリューション」は意味がありません。しかし、「画面にハーフレンダリングされたグラフィックスを描画したくない」などの問題がある場合は、パターンが存在する可能性があります。


いい答え、ありがとう。それでも、最後の段落で書いたことがここに当てはまるとは完全には確信していません。質問の内容を編集して、意味を説明しました。
フランクパファー2016年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.