依存性注入とシングルトンデザインパターン


88

依存性注入またはシングルトンパターンをいつ使用するかをどのように識別しますか。「シングルトンパターンよりも依存性注入を使用する」と書かれている多くのWebサイトを読んだことがあります。しかし、私がそれらに完全に同意するかどうかはわかりません。私の中小規模のプロジェクトでは、シングルトンパターンの使用は間違いなく簡単です。

たとえば、ロガー。使用できますLogger.GetInstance().Log(...) が、これの代わりに、作成したすべてのクラスにロガーのインスタンスを挿入する必要があるのはなぜですか?

回答:


65

テストに何が記録されるかを確認したい場合は、依存性注入が必要です。さらに、ロガーがシングルトンになることはめったにありません。通常、クラスごとにロガーがあります。

テスト容易性のためのオブジェクト指向設計に関するこのプレゼンテーションを見ると、シングルトンが悪い理由がわかります。

シングルトンの問題は、特にテストでは、予測が難しいグローバル状態を表すことです。

オブジェクトは事実上のシングルトンである可能性がありますが、それでもSingleton.getInstance()。ではなく依存性注入を介して取得されることに注意してください。

MiskoHeveryのプレゼンテーションで重要なポイントをいくつか挙げています。それを見た後、依存関係が何であるかをオブジェクトに定義させる方が良い理由について完全な見通しを得ることができますが、それらを作成する方法を定義することはできません。


89

シングルトンは共産主義のようなものです。どちらも紙の上では素晴らしいように聞こえますが、実際には問題が発生して爆発します。

シングルトンパターンは、オブジェクトへのアクセスのしやすさを不釣り合いに強調します。すべてのコンシューマーがAppDomainスコープのオブジェクトを使用することを要求することにより、コンテキストを完全に回避し、さまざまな実装のオプションを残しません。インフラストラクチャの知識をクラスに組み込み(への呼び出しGetInstance())、表現力をまったくゼロにします。1つのクラスで使用される実装を、すべてのクラスで変更せずに変更することはできないため、実際には表現力が低下します。1回限りの機能を追加することはできません。

また、クラスがにFoo依存しているLogger.GetInstance()場合、Fooはその依存関係をコンシューマーから効果的に隠しています。これは、Fooそのソースを読んで、それがに依存しているという事実を明らかにしない限り、それを完全に理解したり、自信を持って使用したりできないことを意味しますLogger。ソースがない場合は、依存しているコードを十分に理解して効果的に使用できるかどうかが制限されます。

静的プロパティ/メソッドで実装されたシングルトンパターンは、インフラストラクチャの実装に関するハックにすぎません。それは無数の方法であなたを制限しますが、代替案に対して認識できる利益を提供しません。好きなように使用できますが、より良いデザインを促進する実行可能な代替手段があるため、推奨される方法ではありません。


9
@BryanWattsとはいえ、シングルトンは通常の中規模アプリケーションではまだはるかに高速でエラーが発生しにくいです。通常、(カスタム)ロガーの可能な実装は複数ないので、**が必要な理由は次のとおりです。1。そのためのインターフェイスを作成し、パブリックメンバーを追加/変更する必要があるたびに変更します。2。維持するDI構成3.システム全体にこのタイプの単一のオブジェクトがあるという事実を隠します。4。関心の分離が時期尚早であるために発生する厳密な機能に自分自身をバインドします。
Uri Abramson 2014

@UriAbramson:あなたはあなたが好むトレードオフについてすでに決定を下しているようですので、私はあなたを説得しようとはしません。
ブライアンワッツ

@BryanWattsそうではない、おそらく私のコメントは少し厳しすぎたが、私が提起した点についてあなたが言わなければならないことを聞くことは実際に私に多くの興味を持っている...
Uri Abramson 2014

1
@UriAbramson:まあまあです。少なくとも、テスト中の分離のために実装を交換することが重要であることに同意しますか?
ブライアンワッツ

5
シングルトンと依存性注入の使用は相互に排他的ではありません。シングルトンはインターフェイスを実装できるため、別のクラスへの依存関係を満たすために使用できます。シングルトンであるという事実は、すべてのコンシューマーがその「GetInstance」メソッド/プロパティを介して参照を取得することを強制するわけではありません。
オリバー

18

他の人は、一般的なシングルトンの問題を非常によく説明しています。Loggerの特定のケースについてのメモを追加したいと思います。静的getInstance()またはgetRootLogger()メソッドを介して、シングルトンとしてロガー(正確にはルートロガー)にアクセスすることは通常問題ではないことに同意します。(テストしているクラスによって何がログに記録されるかを確認したい場合を除きますが、私の経験では、これが必要だったケースを思い出すことはほとんどできません。また、他の人にとっては、これはより差し迫った懸念事項かもしれません)。

IMOには通常、テストしているクラスに関連する状態が含まれていないため、シングルトンロガーは心配ありません。つまり、ロガーの状態(およびその可能な変更)は、テストされたクラスの状態にはまったく影響しません。したがって、ユニットテストがこれ以上難しくなることはありません。

別の方法は、コンストラクターを介して、アプリ内の(ほぼ)すべてのクラスにロガーを挿入することです。代わりにあなたがいることをいくつかの点で発見したときにということでしょう-インタフェースの一貫性を保つため、問題のクラスは、現時点では何もログインしていない場合でも、注入されるべきで、今ので、あなたがこのクラスから何かをログに記録する必要がある、あなたはロガーを必要としますDIのコンストラクターパラメーターを追加して、すべてのクライアントコードを壊す必要があります。私はこれらのオプションの両方が嫌いであり、ロギングにDIを使用することは、具体的なメリットなしに、理論的なルールに準拠するために私の人生を複雑にするだけだと感じています。

つまり、私の結論は次のとおりです。(ほぼ)普遍的に使用されているが、アプリに関連する状態を含まないクラスは、シングルトンとして安全に実装できます


1
それでも、シングルトンは苦痛になる可能性があります。場合によっては、ロガーに追加のパラメーターが必要であることに気付いた場合は、新しいメソッドを作成する(そしてロガーを醜くする)か、ロガーのすべてのコンシューマーを気にしない場合でも壊します。
kyoryu 2010

4
@kyoryu、私は「通常の」ケースについて話している。これは、IMHOが(事実上の)標準のロギングフレームワークを使用することを意味している。(これは通常、プロパティ/ XMLファイルを介して構成可能です。)もちろん、例外があります-いつものように。私のアプリがこの点で例外的であることがわかっている場合、私は実際にシングルトンを使用しません。しかし、「これはいつか役立つかもしれない」と言う過剰設計は、ほとんどの場合、無駄な努力です。
ペーテルTörök

すでにDIを使用している場合は、それほど余分なエンジニアリングは必要ありません。(ところで、私はあなたに同意しません、私は賛成です)。多くのロガーは、いくつかの「カテゴリ」情報などを必要とし、パラメータを追加すると問題が発生する可能性があります。インターフェイスの背後に非表示にすると、消費するコードをクリーンに保つことができ、別のロギングフレームワークへの切り替えが容易になります。
kyoryu 2010

1
@kyoryu、間違った仮定でごめんなさい。反対票とコメントを見たので、ドットを接続しました-間違った方法でした:-(別のロギングフレームワークに切り替える必要性を経験したことはありませんが、それが正当なものである可能性があることを理解していますいくつかのプロジェクトで心配。
ペーテルTörök

5
私が間違っている場合は訂正してください。ただし、シングルトンはLogFactory用であり、ロガー用ではありません。さらに、LogFactoryはApacheCommonsまたはSlf4jロギングファサードである可能性があります。したがって、ロギングの実装を切り替えるのはとにかく簡単です。DIを使用してLogFactoryを挿入することの本当の苦痛は、アプリ内のすべてのインスタンスを作成するために今すぐapplicationContextに移動する必要があるという事実ではありませんか?
HDave 2010年

9

それはほとんどですが、テストについては完全ではありません。シングルトンは使いやすいという理由で人気がありましたが、シングルトンにはいくつかの欠点があります。

  • テストするのは難しい。ロガーが正しいことをすることをどのように確認するかという意味です。
  • テストするのは難しい。つまり、ロガーを使用するコードをテストしているが、それがテストの焦点では​​ない場合でも、テスト環境がロガーをサポートしていることを確認する必要があります
  • シングルトンが必要ない場合もありますが、柔軟性が高くなります

DIを使用すると、依存クラスを簡単に使用できます。コンストラクター引数に入れるだけで、システムが提供しますが、テストと構築の柔軟性も提供します。


あんまり。それは、共有された可変状態と静的な依存関係についてであり、長期的には物事を苦痛にします。テストは明白であり、しばしば最も苦痛な例です。
kyoryu 2010

2

依存性注入の代わりにシングルトンを使用する必要があるのは、シングルトンがList.Emptyなどの不変の値を表す場合だけです(不変のリストを想定)。

シングルトンのガットチェックは、「これがシングルトンではなくグローバル変数であったとしても大丈夫でしょうか?」である必要があります。そうでない場合は、シングルトンパターンを使用してグローバル変数を処理しているため、別のアプローチを検討する必要があります。


1

モノステートの記事をチェックしてください-これはシングルトンの気の利いた代替手段ですが、いくつかの奇妙な特性があります:

class Mono{
    public static $db;
    public function setDb($db){
       self::$db = $db;
    }

}

class Mapper extends Mono{
    //mapping procedure
    return $Entity;

    public function save($Entity);//requires database connection to be set
}

class Entity{
public function save(){
    $Mapper = new Mapper();
    $Mapper->save($this);//has same static reference to database class     
}

$Mapper = new Mapper();
$Mapper->setDb($db);

$User = $Mapper->find(1);
$User->save();

マッパーはsave()を実行するためにデータベース接続に実際に依存しているため、この種の恐ろしいことではありませんが、別のマッパーが以前に作成されている場合は、依存関係を取得する際にこのステップをスキップできます。きちんとしていますが、それもちょっと面倒ですよね?


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