web-dev-qa-db-ja.com

非常に異なるコンストラクターでオブジェクトをインスタンス化する場合、ファクトリパターンを使用する必要がありますか?

(例の目的で)IShapeを実装する3つのクラスがあるとしましょう。 1つは、Square(int length)のコンストラクターを持つSquareです。 2つ目は、Triangle(int base、int height)のコンストラクタを持つTriangleです。 3番目は、Circle(double radius)のコンストラクターを持つCircleです。

すべてのクラスが同じインターフェースを共有していることを考えると、私の心は、使用する作成パターンとしてファクトリーパターンに行きます。しかし、ファクトリーメソッドは、これらのさまざまなコンストラクターのパラメーターを提供する必要があるため、扱いにくくなります。たとえば、次のようになります。

IShape CreateShape(int length, int base, int height, double radius)
{
    ...
    return new Circle(radius);

    ...
    return new Triage(base, height);

    ...
    return new Square(length);
}

このファクトリーメソッドはかなり扱いにくいようです。これは、抽象ファクトリーやその他のデザインパターンが優れたアプローチとして機能する場所ですか?

6
Craig

あなたは問題を探している解決策を持っています、それがあなたがトラブルに遭遇する理由です。

ファクトリーメソッドはそれ自体が目的ではなく、目的への手段です。したがって、解決したい問題を特定する必要がありますfirst、つまり、これらのオブジェクトを構築するためにユースケースが必要であり、必要なコンテキストを提供します。お気に入り:

  • オブジェクトの説明を含むファイルストリームやデータベースなどの外部データソースがある

  • ファクトリでこのデータソースからIShapeオブジェクトを作成する必要があります(したがって、シェイプのリストが拡張された場合に変更するコードの場所が1つだけです)

たとえば、「ファイルストリーム」のコンテキストでは、CreateShapeファクトリメソッドは、おそらく1つのオブジェクトの説明(CSV文字列、JSON文字列、またはXMLスニペット)を含むパラメータとして文字列を取得し、要件は、その文字列を解析して正しいオブジェクトを作成することです。

IShape CreateShape(string shapeDescription)
{
    switch(getShapeType(shapeDescription))
    {
      case "Circle":
          radius=parseRadius(shapeDescription);
          return new Circle(radius);

      case "Triangle":
          base=parseBase(shapeDescription);
          height=parseHeight(shapeDescription);
          return new Triangle(base, height);
    ...

}

さて、このメソッドのパラメーターリストは、それほど厄介に見えなくなりましたね。

その他の潜在的な使用例:

  • 形状はユーザー入力に基づいて作成されます:ファクトリーはパラメーターとしてユーザー入力データの一部を取得します

  • 動的なビジネスロジックに基づいて形状を作成する

また、機能以外のその他の要件も考慮する必要があります。

  • ファクトリにその外部データソースからの分離を支援してほしいですか?たとえば、単体テストでは?次に、単なるメソッドではなく、モックアウトできるインターフェイスを持つクラスにします。

  • 新しい形状を追加する必要がある場合でもコードに手を加える必要がない、オープン/クローズの原則に従って、ファクトリー自体を再利用可能なコンポーネントにしたいですか?次に、リフレクション、ジェネリックス、 プロトタイプパターン 、または 戦略パターン のいずれかを使用して、より一般的な方法でビルドする必要があります。

そして、はい、特定のユースケースでは、おそらくファクトリーメソッドはまったく必要ありません。

つまり、最初に要件を明確にします。ファクトリメソッドを使用するためのコンテキストがわからない場合は、まだ必要ありません。

17
Doc Brown

工場クラス

いくつかのメソッドを持つことができるファクトリーclassを使用します。ファクトリーには独自のインターフェースが必要です。

interface IShapeFactory
{
    IShape CreateRectangle(float width, float height);
    IShape CreateCircle(float radius);
}

class ShapeFactory : IShapeFactory
{
    ///etc....

ジェネリックファクトリーメソッド

ファクトリーmethodに固執し、ジェネリック型パラメーターを使用して型をパラメーター化する場合は、入力をジェネリック型にするために少し作業を行う必要があります。

トリックは、入力パラメーターのインターフェースを定義することです(例:IShapeArgsFor<T>)。インターフェイスはTに関連付けられているため、コンパイラは残りを推測できます。

T CreateShape<T>(IShapeArgsFor<T> input) where T : IShape

による支援

interface IShapeArgsFor<T> where T : IShape
{
}

そして

class CircleArgs : IShapeArgsFor<Circle>
{
    public float Radius { get; }
}

class RectangleArgs : IShapeArgsFor<Rectangle>
{
    public float Height { get; }
    public float Width { get; }
}

等....

次に、次のように呼び出します。

var circle = CreateShape(new CircleArgs { Radius = 3 });
7
John Wu

このファクトリーメソッドはかなり扱いにくいようです。

ファクトリを呼び出すクライアントコードは、すべての可能な形状のパラメーターを渡す必要があります。さらに、目的の形状に当てはまらないパラメーターは、スタブ化する必要があります。三角形を取得するには、呼び出しは

CreateShape(length: 0, base: 21, height: 42, radius: 0)  // returns a triangle
// 0 or a negative number is a special value

呼び出し側のコードは、どのパラメーターをスタブするかをどのようにして知るのでしょうか?
2つのオプションがあります。

  • 呼び出しコードは知りません。どこかで形状データを取得し、それをファクトリーに渡します。入力形状データには、必要なすべてのスタブがすでに含まれています。
    これは有効なシナリオです。

  • 呼び出しコードはスタブを追加します。事実上、呼び出し元のコードは、どのような形が必要かを "知っている"必要があります(そうでない場合、不要なパラメーターがわかりません)。
    それは工場の目的に反することになります。

2
Nick Alexeev