ファーストクラスの機能は、戦略パターンの代替品ですか?


15

戦略デザインパターン、多くの場合、それらが不足している言語のファーストクラスの機能の代替としてみなされています。

たとえば、機能をオブジェクトに渡したいとします。Javaでは、目的の動作をカプセル化する別のオブジェクトをオブジェクトに渡す必要があります。Rubyなどの言語では、機能自体を匿名関数の形式で渡すだけです。

しかし、私はそれについて考えていたので、多分Strategyは単なる匿名関数が提供する以上のものを提供すると決めました。

これは、オブジェクトがメソッドの実行期間とは無関係に存在する状態を保持できるためです。ただし、匿名関数自体は、関数の実行が終了した時点で存在しなくなる状態のみを保持できます。

ファーストクラスの関数をサポートするオブジェクト指向言語では、関数を使用するよりも戦略パターンに利点がありますか?


10
「ただし、匿名関数自体は、関数の実行が終了した時点で存在しなくなる状態のみを保持できます。」:これは事実ではありません。クロージャーは、異なる呼び出しにわたって存続する状態を保持できます。
ジョルジオ14

「しかし、匿名関数自体は、関数が実行を終了した瞬間に存在しなくなる状態しか保持できません。」:グローバル変数、および静的変数を少なくとも忘れないでください。
gbjbaanb 14

回答:


13

言語が関数への参照をサポートしている場合(Javaはバージョン8)、これらは通常、より少ない構文で同じことを表現するため、戦略の優れた代替手段です。ただし、実際のオブジェクトが役立つ場合があります。

Strategyインターフェースには複数のメソッドを含めることができます。RouteFindingStragegyさまざまなルート検索アルゴリズムをカプセル化するインターフェースを例に取りましょう。次のようなメソッドを宣言できます

  • Route findShortestRoute(Node start, Node destination)
  • boolean doesRouteExist(Node start, Node destination)
  • Route[] findAllPossibleRoutes(Node start, Node destination)
  • Route findShortestRouteToClosestDestination(Node start, Node[] destinations)
  • Route findTravelingSalesmanRoute(Node[] stations)

その後、すべてが戦略によって実装されます。一部のルート検索アルゴリズムでは、これらのユースケースの一部に対して内部最適化が許可される場合と許可されない場合があるため、実装者はこれらの各メソッドの実装方法を決定できます。

別のケースは、戦略に内部状態がある場合です。もちろん、一部の言語ではクロージャーは内部状態を持つことができますが、この内部状態が非常に複雑になると、多くの場合、クロージャーを本格的なクラスに昇格させることがよりエレガントになります。


2
「この内部状態が非常に複雑になると、閉鎖を本格的なクラスにプロモートすることがしばしば有用になります」:なぜですか?内部状態が複雑になった場合は、クロージャー内に格納されているオブジェクト/レコードに入れることもできます。
ジョルジオ14

1
@Giorgioしかし、そうすると、クロージャとその内部状態を管理するクラスという2つの構文エンティティを維持することになります。そのため、クロージャーをそのクラスに移動することでコードを簡素化できます。これは、正確な使用例、プログラミング言語、および個人的な好みに応じて、改善される場合と改善されない場合があります。
フィリップ14

それは私にとって合理的な見方のようです。
ジョルジオ14

5

匿名関数は、関数の実行が終了したときに存在しなくなる状態しか保持できないのは事実ではありません。

Common Lispで次の例を取り上げます。

(defun number-strings (ss)
  (let ((counter 0))
    (mapcar #'(lambda (s) (format nil "~a: ~a" (incf counter) s)) ss)))

この関数は、文字列のリストを取得し、リストの各要素にカウンターを追加します。したがって、たとえば、呼び出し

(number-strings '("a" "b" "c"))

与える

("1: a" "2: b" "3: c")

この関数は、関数が呼び出されるたびに再利用される状態(カウンターの現在の値)を保持number-stringsする変数counterを持つ匿名関数を内部的に使用します。

一般に、クロージャはメソッドが1つしかないオブジェクトと考えることができます。または、オブジェクトは、同じクローズド変数を共有するクロージャーのコレクションです。したがって、クロージャの代わりにオブジェクトを使用する必要がある場合があるかどうかはわかりません。どちらも異なる視点から同じパターンを見る方法であると主張します。

特に、ストラテジーパターンにはメソッドが1つしかないオブジェクトが必要なので、クロージャーがジョブを実行する必要があります。しかし、Philippが答えで観察したように、状況(複雑な状態)およびプログラミング言語によっては、オブジェクトを使用することでよりエレガントなソリューションを得ることができます。


クロージャーとしてファーストクラスの機能をサポートする言語では、「クラシック」戦略を使用することはありますか?
アビブコーン14

1
私はフィリップに同意する傾向があります:それは言語と個人的な好みに依存します。表記をできるだけ単純にするアプローチを常に選択します。たとえば、Lispでは、状態を変数aのリストとしてlet定義し、その中にクロージャを定義できます。基本的に、1つのメソッドでオブジェクトをオンザフライで定義します。別の言語(Javaなど)では、状態を保持する適切なオブジェクトを定義する方が便利(構文的に単純)な場合があります。だから、私はケースからケースに決定します。
ジョルジオ14

1

2つの設計が同じ問題を解決できるからといって、それらが互いに直接代用されるわけではありません。

関数型プログラムで状態を追跡する必要がある場合は、言語で許可されていても、closed over変数を変更しないでください。1つの状態を引数として取り、戻り値として新しい状態を返す関数を呼び出すように手配します。

アーキテクチャは大きく異なりますが、同じ目標を達成できます。あるパラダイムのパターンを他のパラダイムに直接押し付けようとしないでください。


「関数型プログラムで状態を追跡する必要がある場合は、言語で許可されていても、closed over変数を変更しないでください。」:私は純粋に関数型のファンであり、あなたのアドバイスに同意します。一方、クロージャーは機能的な構成だけでなく、純粋に機能的なものです。字句コンテキストから変数を閉じるという考え方は、参照の透過性/不変性に直交しています。
ジョルジオ14

申し訳ありませんが、私はあなたの議論に従うことはできません。純粋に機能的なプログラミングでの状態処理は、目の前の質問と何の関係がありますか?
フィリップ14

1
ポイントは、ファーストクラス関数のように、機能パラダイムの一部を使用する場合、パラダイムの他の部分を引き込んでスムーズに機能させる必要がある場合でも驚かないでください。
カールビーレフェルト14

1

戦略は概念であり、特定の繰り返し発生する問題を解決するための便利なレシピです。これは言語の構成要素ではなく、実装の 1つの形式に関するものでもありません。閉鎖は、ある日戦略を実装し、翌日にオブザーバーを実装するために使用できます。

長期戦略は、簡潔にあなたの意図を表現するために、他のプログラマーとの会話では、主に有用です。それについて魔法のようなものは何もありません。


2
この質問では、特定のクラス構造を持つ戦略設計パターンに特に言及しています。特定の目標を達成するための行動計画としての「戦略」の他の意味は、この文脈では不正確です。

私は@Snowmanに同意する傾向があります。戦略パターンについて話しているのですか?
ローワンフリーマン14

1
@Snowman、あなたがリンクしたページでさえ、このパターンの実装方法を正確に述べておらず、特定の言語で例を挙げていますが、UMLダイアグラムにはC ++継承、Javaインターフェース、またはRubyブロックを使用する必要があるとは記載されていません。それで、私はあなたの分析に親切に反対します。
idoby 14
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.