web-dev-qa-db-ja.com

基本クラスの静的ファクトリメソッド

ファクトリメソッドのますます一般的な定義は、そのクラスの型のオブジェクトを返すクラスの静的メソッドです。ただし、コンストラクターとは異なり、コンストラクターが返す実際のオブジェクトはサブクラスのインスタンスである場合があります。 enter image description here

差出人: https://sourcemaking.com/design_patterns/factory_method

OCPに違反していませんか?

私はこれも製品コードで見ました。

正しい実装は何でしょうか?

サブクラスオブジェクトを返す静的メソッドを持つ別のCreatorクラスを考えることができます。私の提案するソリューションは、製品のファミリーを作成しないため、明らかに抽象的なファクトリーではなく、GoF作成パターンのいずれにも適合しません。だから私は自分の解決策に懐疑的です。

7
q126y

Open/Close Principe(OCP) によると:

ソフトウェアエンティティ(クラス、モジュール、関数など)は、拡張のために開いている必要がありますが、変更のために閉じている必要があります。

この観点から静的ファクトリアーキテクチャを調べてみましょう。

  • デザインをサブクラスProductThreeで水平方向にextendしたいと想像してください。サブクラスのコンストラクターを知らなくても、サブクラスの1つであるオブジェクトをmakeProduct()が返すにはどうすればよいですか?私は2つのアプローチを考えることができます:
    • 巨大なswitchまたはチェーンされたifとして実装されている場合は、メソッドを変更する必要があります。これはOCPを侵害します。
    • Productをそのサブクラスから分離する自己登録メカニズムを考えることができます(たとえば、マップを使用する- 連想コンテナ クラス名/識別子をクラスメーカーメソッドでマッピングする Javaの例 )。これは拡張性の要件を満たし、OCPへの準拠を維持します。
  • ProductOneProductOneAProductOneBに特殊化することにより、デザインを垂直方向に拡張したいと想像してください。 。これを実現するために、makeProduct()のサブクラスコンストラクターに依存することはできませんでした。 makeProductOne()を使用する必要があります。これには、階層を介してカスケードできるように自己登録を設計する必要があります。これは難しいかもしれませんが、不可能ではないようです。
  • 設計に自己登録要件を実装する方法を見つけることができたとします。この場合、OCPに準拠して、ファクトリメソッドを完全にシールできます。

結論として、静的ファクトリーの概念はOCPに準拠しています。もちろん、特定の実装はOCPを侵害する可能性がありますが、それらは私が前述した追加の要件を考慮に入れない場合に限られます。

ファクトリクラスを使用する方がよりクリーンなアプローチであることに注意してください。これは 懸念の分離 を強制します(つまり、ファクトリが製品の内部について知っていることを避けます)。ただし、OCPに関しては、上記で説明した静的メソッドを使用することは、受け入れ可能な代替手段です。

他の言語についてはわかりませんが、Andrei Alexandrescuが "Modern C++ design-Generic Programming and design patterns applied" でファクトリメソッドの実装を示しました=。マップと登録/登録解除機能を使用した完全なOCPです。彼が言及する唯一の難しさは、工場に提供しなければならないタイプ識別子です。もちろん、enumはOCPの問題ですが、この問題を回避するために、RTTIまたは一意のIDジェネレーターを使用していくつかの代替案を検討しました。

8
Christophe

GoFブックにリストされている静的ファクトリーメソッドが表示されない理由は、このパターンがポリモーフィズムを興味深い方法で使用していないためです。あなたの図はこれを示唆していますが、ほとんどの言語はそれが示す構造をサポートしていません。具体的には、静的メソッドを仮想にすることもできません。ディスパッチするインスタンスオブジェクトはありません。インスタンスメソッドをオーバーライドする方法で静的メソッドをオーバーライドすることはできません(一部の言語にはクラスメソッドがありますが、クラスオブジェクトでディスパッチできます)。 GoFの本ではオブジェクト指向パターンの紹介のみを取り上げていますが、静的ファクトリーメソッドは多くの言語で依然として一般的なパターンです。

リンク先のページで説明しているのは、2つの異なるパターンです。

まず、クラスのコンストラクターを公開するのではなく、静的メソッドを提供するという共通のパターンがあります。多くの言語では、複数のコンストラクタを定義し、メソッドのオーバーロードによってそれらを解決できますが、静的メソッドには異なる名前を付けることができるため、はるかにプログラマーフレンドリーです。リンク先のページでは、例としてColorオブジェクトを使用しています。 new Color(float, float, float)は明白かもしれませんが、さまざまなカラーモデル(RGBとHSV)でオーバーロードできません。静的メソッドは、Color::make_rgb(float, float, float)Color::make_hsv(float, float, float)という名前で一義化できます。これは優れたAPI設計についてですが、オブジェクト指向とは何の関係もありません。

(静的)ファクトリーメソッドを使用すると、実装の自由度も高くなります。 HSVカラーを内部的にRGBにマップできます。または、Colorは抽象的であり、この違いをユーザーに公開することなく、色空間ごとに異なるサブクラスを使用できます。この実装の詳細は完全にカプセル化されています。

public static Color makeRGB(float r, float g, float b) {
  return new ColorRGB(r, g, b);
}

public static Color makeHSV(float h, float s, float v) {
  return new ColorHSV(h, s, v);
}

私の経験では、このようなメソッドによりコードが曖昧になりにくくなり、APIがより親しみやすく、保守しやすくなります。 C++では、静的メソッドがテンプレート引数の控除を使用できるという追加の利点がありますが、クラステンプレートパラメーターはコンストラクター呼び出しで明示的に提供する必要があります。

ファクトリーメソッドには別の興味深い機能があります。インスタンス化する具体的な型をコードで決定できます。私はこれをいくつかのパーサーで使用しました:

Expression makeBinaryExpression(Expression left, String operator, Expression right) {
  if (operator == "+") return new Addition(left, right);
  if (operator == "-") return new Subtraction(left, right);
  if (operator == "*") return new Multiplication(left, right);
  if (operator == "/") return new Division(left, right);
  throw new ParseException("Expected +, -, *, /, but got " + operator);
}

そのような静的メソッドが拡張性の問題を引き起こすことは正しいです。 CMYKカラーモデルをColorクラスに追加するにはどうすればよいですか? Colorクラスを編集したり、(インポートを変更したり、C++テンプレートを使用して)互換性のある別のクラスに置き換えたりしないと、できません。拡張性が必要な場合は、APIを仮想化する必要があります。すぐに戻ってきます。

ただし、すべてのコードがポリモーフィックである必要はありません。特に、振る舞いが「純粋」であり、外部の状態と相互作用しない場合、および振る舞いが変化する可能性がない場合でも、非仮想メソッドはまだ場所を持っています。

また、オープン/クローズの原則は、ソースを変更して新しいバージョンをリリースできない場合にのみ関係します。これは、パブリッシャーとコンシューマーが異なる組織にある公開APIの場合です。そのような場合、明確なAPIが必要であり、このインターフェースは、消費者がニーズに適応できるように慎重に設計する必要があります。多くの場合、依存性注入を必要とし、ユーザーが実装可能なインターフェースに関してAPIを一貫して表現することによります。ただし、コードが同じプロジェクト内でのみ使用されている場合、OCPはそれほど重要ではありません(ただし、インターフェイスに依存していると、リファクタリングが容易になります)。

次に、ポリモーフィズムに戻りましょう。 GoFの本には、ファクトリー・メソッド・パターンがリストされており、「仮想コンストラクター」に例えられます。ここでの要点は、ファクトリーメソッドは仮想でオーバーライド可能であることです。もちろん、これを作成するには既存のProductが必要なので、Productクラスのファクトリメソッドは役に立たないことを意味します。代わりに、別のCreatorクラスがあります。ファクトリメソッドは、特定のProductサブクラスを提供するためにCreatorサブクラスによってオーバーライドできるテンプレートメソッドパターンの例です。抽象ファクトリパターンは、関連する複数のファクトリメソッドを持つ作成者の例です。

時折、ファクトリメソッドは依存関係注入メカニズムとしても使用されます。基本クラスは、依存関係を構築するためのいくつかのテンプレートメソッドを定義します。依存関係は、サブクラス化によって変更できます。ただし、私の経験では、小さなStrategyオブジェクトを使用して依存関係の構築を開始する方が、ターゲットクラス全体を継承する必要がなく、より柔軟で単純なアプローチであることがわかっています。

2
amon