オブジェクトをコンストラクターに渡すか、クラスでインスタンス化する必要がありますか?


10

次の2つの例を検討してください。

オブジェクトをコンストラクターに渡す

class ExampleA
{
  private $config;
  public function __construct($config)
  {
     $this->config = $config;
  }
}
$config = new Config;
$exampleA = new ExampleA($config);

クラスのインスタンス化

class ExampleB
{
  private $config;
  public function __construct()
  {
     $this->config = new Config;
  }
}
$exampleA = new ExampleA();

プロパティとしてのオブジェクトの追加を処理する正しい方法はどれですか?いつどちらを使用すればよいですか?単体テストは私が使用するべきものに影響しますか?



9
@FrustratedWithFormsDesigner-これはコードレビューには適していません。
ChrisF

回答:


14

最初のものは、config他の場所でオブジェクトを作成し、それをに渡すことができると思いますExampleA。依存性注入が必要な場合、すべてのインスタンスが同じオブジェクトを共有することを保証できるため、これは良いことです。

一方、2番目の例の方が適切な場合(各インスタンスの構成が異なる可能性がある場合など)があるため、新しいクリーンなオブジェクトExampleA が必要にconfigなる場合があります。


1
したがって、Aは単体テストに適しており、Bは他の状況に適しています... 多くの場合、2つのコンストラクターがあります。1つは手動DI用で、もう1つは通常の使用法です(私はUnityをDIに使用します。これにより、DI要件を満たすコンストラクターが生成されます)。
エドジェームス

3
@EdWoodcockそれは実際にはユニットテストと他の状況ではありません。オブジェクトは外部からの依存関係を必要とするか、内部で管理します。両方/どちらでもありません。
MattDavey 2012年

9

テスト容易性を忘れないでください!

通常、Exampleクラスの動作が構成に依存する場合、クラスインスタンスの内部状態を保存/変更せずにテストできるようにする必要があります(つまり、プロパティを変更せずに、ハッピーパスと間違った構成の簡単なテストを行う必要があります/ Exampleクラスのメンバー)。

したがって、私は最初のオプションで行きます ExampleA


それが私がテストについて言及した理由です-それを追加してくれてありがとう:)
囚人

5

オブジェクトに依存関係の存続期間管理する責任がある場合は、コンストラクターでオブジェクトを作成(およびデストラクターで破棄)しても問題ありません。*

オブジェクトが依存関係の存続期間を管理する責任を負わない場合は、オブジェクトをコンストラクターに渡し、外部から(たとえば、IoCコンテナーによって)管理する必要があります。

この場合、ClassAがそれを破棄する責任がある場合、または構成がClassAのインスタンスごとに一意である場合を除いて、ClassAが$ configを作成する責任を負うべきではないと私は思います。

*テスト容易性を支援するために、コンストラクターはファクトリークラス/メソッドへの参照を取得して、コンストラクター内の依存関係を構築し、凝集性とテスト容易性を向上させることができます。

//Object which manages the lifetime of its dependency (C#):
public class ClassA : IDisposable
{
    public Config Config { get; private set; }

    public ClassA()
    {
        this.Config = new Config(); // Tightly coupled to Config class...
    }

    public void Dispose()
    {
        this.Config.Dispose();
    }
}

// Object which does not manage its dependency:
public class ClassA
{
    public Config Config { get; set; }

    public ClassA(Config config) // dependency may be injected...
    {
        this.Config = config;
    }
}

// Object which manages its dependency in a testable way:
public class ClassA : IDisposable
{
    public Config Config { get; private set; }

    public ClassA(IConfigFactory configFactory) // dependency may be mocked...
    {
        this.Config = configFactory.BuildConfig();
    }

    public void Dispose()
    {
        this.Config.Dispose();
    }
}

2

私は最近、これとまったく同じ議論をアーキテクチャチームと行ってきましたが、何らかの方法でそれを行うには微妙な理由があります。これは主に、依存関係の注入(他の人が指摘したとおり)と、コンストラクターで新規に作成するオブジェクトの作成を本当に制御できるかどうかによって決まります。

あなたの例では、あなたのConfigクラスはどうですか?

a)プールやファクトリーメソッドからの割り当てなど、重要な割り当てがあります。

b)割り当てに失敗する可能性があります。それをコンストラクタに渡すことで、この問題をうまく回避できます。

c)は実際にはConfigのサブクラスです。

オブジェクトをコンストラクターに渡すと、最も柔軟性が高まります。


1

以下の答えは間違っていますが、私は他の人がそれから学ぶためにそれを保ちます(以下を参照)

ではExampleAConfig複数のクラスで同じインスタンスを使用できます。ただし、Configアプリケーション全体でインスタンスが1つしかない場合は、シングルトンパターンを適用して、のConfigインスタンスが複数存在しないようにすることを検討してくださいConfigConfigシングルトンの場合は、代わりに次のようにできます。

class ExampleA
{
  private $config;
  public function __construct()
  {
     $this->config = Config->getInstance();
  }
}
$exampleA = new ExampleA();

ExampleB他の一方で、あなたはいつもの別個のインスタンス買ってあげるConfigの各インスタンスについてをExampleB

どのバージョンを適用する必要があるかは、アプリケーションが次のインスタンスを処理する方法によって異なりますConfig

  • の各インスタンスにExampleXの個別のインスタンスが必要な場合はConfig、に進みExampleBます。
  • の各インスタンスがのインスタンスをExampleX1つだけ共有する場合はConfig、を使用しExampleA with Config Singletonます。
  • のインスタンスがのExampleX異なるインスタンスを使用する可能性がある場合はConfig、に固執しExampleAます。

シングルトンConfigへの変換が間違っている理由:

昨日はシングルトンパターンについてのみ学んだことを認めなければなりません(デザインパターンのヘッドファーストの本を読んでいます)。単純に私はこの例にそれを適用しましたが、多くの人が指摘したように、ある方法は別の方法です(いくつかはより不可解で、「あなたはそれを間違っている!」とだけ言っています)、これは良い考えではありません。したがって、私がしたのと同じ過ちを他の人がするのを防ぐために、シングルトンパターンが有害である理由の概要を次に示します(コメントと私がそれをグーグルで見つけたものに基づいて):

  1. 場合ExampleAに独自の基準取得Configのインスタンスを、クラスが緊密に結合されます。のインスタンスExampleAを使用してConfig(のサブクラスなど)別のバージョンを使用する方法はありません。に提供する方法がないためExampleA、のモックアップインスタンスを使用してテストする場合、これは恐ろしいことです。ConfigExampleA

  2. 前提は1、そして唯一のだろう、のインスタンスがConfig多分保持し、今、あなたは常に同じ意志ホールドていることを確認することはできません将来的には。後でいくつかのインスタンスConfigが望ましいことが判明した場合、コードを書き直さずにこれを実現する方法はありません。

  3. の唯一無二のインスタンスがConfigすべての永遠に当てはまるかもしれませんが、Config(まだインスタンスが1つしかない)のサブクラスを使用できるようにしたい場合があります。ただし、コードはメソッドであるgetInstance()of を介してインスタンスを直接取得するため、サブクラスを取得Configするstatic方法はありません。ここでも、コードを書き直す必要があります。

  4. ExampleA使用Configするという事実は、少なくとものAPIを表示しているだけでは非表示になりますExampleA。これは悪いことかもしれませんし、そうでないかもしれませんが、個人的には、これは不利に思えます。たとえば、維持する場合、Config他のすべてのクラスの実装を調べずに、変更によって影響を受けるクラスを見つける簡単な方法はありません。

  5. シングルトンExampleA使用すること自体が問題ではない場合でも、テストの観点からは問題になる可能性があります。シングルトンオブジェクトは、アプリケーションの終了まで存続する状態を保持します。あるテストを別のテストから分離したいので、これは問題になる可能性があります(つまり、1つのテストを実行しても、別のテストの結果に影響を与えてはなりません)。これを修正するには、テスト実行のたびに(場合によってはアプリケーション全体を再起動する必要がある)シングルトンオブジェクトを破棄する必要があります。 Config

とはいえ、実際のアプリケーションの実装ではなく、ここでこの間違いを犯してよかったと思います。実際、実際に最新のコードを書き直して、一部のクラスにシングルトンパターンを使用することを検討していました。変更を簡単に元に戻すことはできますが(もちろんすべてがSVNに保存されています)、それでも時間を無駄にしていました。


4
私は...この方法であなたしっかり夫婦クラスそうお勧めしませんExampleAし、Config良いことではありませんています- 。
Paul

@ポール:それは本当です。良いキャッチ、それについて考えていませんでした。
gablin 2012年

3
テスト容易性の理由から、私は常にシングルトンを使用しないことをお勧めします。それらは本質的にグローバル変数であり、依存関係を模倣することは不可能です。
MattDavey 2012年

4
あなたがそれを間違っている理由から、私は常にシングルトンを使用することをお勧めします。
レイノス2012年

1

最も簡単なことは、につなぐExampleAことConfigです。もっと複雑なことをやむを得ない理由がない限り、あなたは最も単純なことをすべきです。

デカップリングのための一つの理由ExampleAConfigのテスト容易性を向上させることであろうExampleA。直接結合は、遅い、複雑、または急速に進化するメソッドを持っているExampleAif のテスト容易性を低下さConfigせます。テストの場合、数マイクロ秒以上実行するとメソッドは遅くなります。のすべての方法Configが単純かつ高速である場合、私は単純なアプローチを採用し、に直接結合ExampleAConfigます。


1

最初の例は、依存性注入パターンの例です。外部依存関係のあるクラスには、コンストラクター、セッターなどによって依存関係が渡されます。

このアプローチにより、疎結合のコードが生成されます。ほとんどの人は疎結合が良いことだと考えています。オブジェクトの特定のインスタンスを他とは異なるように構成する必要がある場合は簡単に構成を置き換えることができ、テスト用にモック構成オブジェクトを渡すことができるためです。オン。

2番目のアプローチは、GRASPクリエーターパターンにより近いものです。この場合、オブジェクトは独自の依存関係を作成します。これにより、コードが密結合され、クラスの柔軟性が制限され、テストがより困難になる可能性があります。クラスの1つのインスタンスに他のインスタンスと異なる依存関係を持たせる必要がある場合、唯一の選択肢はそれをサブクラス化することです。

ただし、依存するオブジェクトの寿命が依存するオブジェクトの寿命によって決定され、依存するオブジェクトがそれに依存するオブジェクトの外部で使用されない場合は、これが適切なパターンになる可能性があります。通常、私はDIをデフォルトの位置にすることをお勧めしますが、その影響を認識している限り、他のアプローチを完全に除外する必要はありません。


0

クラスが$configを外部クラスに公開しない場合は、コンストラクター内で作成します。このようにして、内部状態を非公開にします。

場合は$config必要ですが、使用前に(例えば、データベース接続や初期化、いくつかの内部フィールドを必要とします)を適切に設定する独自の内部状態を必要とし、それはいくつかの外部コード(おそらくファクトリクラス)に初期化を延期し、それを注入する意味がありますコンストラクタ。または、他の人が指摘したように、他のオブジェクト間で共有する必要がある場合。


0

ExampleAは、具象クラスConfigから切り離されています。これは、タイプConfigではなく、抽象スーパークラスのタイプのConfigのオブジェクトを受け取った場合に提供されます。

ExampleBは具象クラスConfig と強く結びついていますが、これは悪いことです。

オブジェクトをインスタンス化すると、クラス間に強い結合が作成されます。これは、Factoryクラスで行う必要があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.