web-dev-qa-db-ja.com

クラス階層内の非常に多くのctorパラメーターの設計パターン

クラス階層コンストラクターのパラメーターが最も下位のクラスに多くのパラメーターを強制する状況を処理するための設計パターンはありますか?

言語が重要であれば、理想的にはこれはC++で行われます。次の一般的な例を検討してください。

class dependency1;
class dependency2;
class dependency3;

class ClassA
{
public:

    ClassA(dependency1* dep1) { }       
};

class ClassB : public ClassA
{
public:

    ClassB(dependency1* dep1, dependency2* dep2) 
      : ClassA(dep1){ }

};

class ClassC : public ClassB
{
public:

    ClassC(dependency1* dep1, dependency2* dep2, dependency3* dep3) 
      : ClassB(dep1, dep2) { }  
};

階層内の各クラスは固有の依存関係を追加し、2つのパラメーターは基本クラスへの受け渡しにのみ使用されますが、最終的には最後のクラスctorに3つのパラメーターが生じます。最後のクラスに11個のパラメーターがあるような、もっと複雑な例を想像できます...

一般にこれに答えるのは難しいかもしれませんが、この問題への一般的な取り組み方を知りたいです。

これに近い唯一のことは、パラメーターが多すぎる状況でビルダーパターンを使用することですが、このケースには適合しないようです(または、クラス階層を単純化するのにどのように役立つかわかりません)。

重複する回答にリダイレクトしてください。見つけられませんでしたが、既に質問されているはずです。

6
Sil

私の見解では、直接が11の要素に依存しているクラスがある場合、多すぎると思われます。

次に、サブクラスの代わりに、ClassCClassB型のメンバーがあったClassA型のメンバーがあった場合にどうなるかを考えます。今回は違いがある以外は、同じ問題が残っているようです。 ClassCClassBに直接依存するべきではありません。代わりに、(周囲のインターフェース)ClassBをパラメーターとして取り込む必要があります。これで、ClassCは、ClassBの依存関係を追加のパラメーターとして取得する必要がなくなりました。 ClassBClassAも同様です。この時点では、各クラスは直接必要な依存関係のみを受け入れます。これには、クラスの結合度と複雑度を示すという利点があります。

依存関係注入コンテナを使用している場合、またはクラスを手動で関連付けている場合、最終的には各クラスがスタンドアロンになります。たとえば、各クラスは、他のクラスが存在しなくてもコンパイルできます(インターフェイスを使用した場合[抽象基本クラス])。ハードな依存関係はありません。実際の依存関係グラフの作成は、トップレベルの手順で行われます。依存関係注入コンテナによって自動的に、または手動で。

サブクラス化に戻ると、コードの設計が不十分でない限り、説明した問題に遭遇することはありません。上記で提案したように、そのようなコードに実行し始めた場合は、何か間違っていることを示しているため、リファクタリングする必要があります。 ClassCが7レベルのサブクラスであるか、最上位のクラスであるかは関係ありません。結局のところ、ClassCには、警告サインとしてとるべき依存関係がすべて必要です。個人的には、深い階層を避けることをお勧めしますが、階層が問題ではないことを明確にしたいと思います。多数の依存関係があります。この場合、C++は、スーパークラス(確かにかなり珍しい機能)によってクラスをパラメーター化できる言語ではありません。これは、コード構造化メカニズムとしての構成ではなく、継承の使用に対する別の引数を提供します。スーパークラスはサブクラスの強い依存関係です。

1つの可能性は、ビルダーのようなオブジェクトの階層を使用することです。

class dependency1;
class dependency2;
class dependency3;

class ClassA {
  public:
    struct Builder {
      dependency1 *dep1 = 0;
    };

    ClassA(const Builder &builder) { ... }
};

class ClassB : public ClassA {
  public:
    struct Builder : ClassA::Builder {
      dependency2 *dep2 = 0;
    };

    ClassB(const Builder &builder) : ClassA(builder) { ... }
};


class ClassC : public ClassB {
  public:
    struct Builder : ClassB::Builder {
      dependency3 *dep3 = 0;
    };

    ClassC(const Builder &builder) : ClassB(builder) { ... }
};

次のように使用します。

  ClassC::Builder builder;
  builder.dep1 = dep1;
  builder.dep2 = dep2;
  builder.dep3 = dep3;

  ClassC c(builder);

これは、典型的なビルダーの Fluent Interface 形式を使用しません。これは、階層で機能させるためにいくつかのトリックが必要になるため、おそらく価値がありません。

2
Vaughn Cato