シングルトンが悪い場合、サービスコンテナはなぜ良いのでしょうか。


91

我々は、すべての方法を知っている悪いシングルトン彼らは依存関係を非表示にするためにためている他の理由

しかし、フレームワークでは、一度だけインスタンス化してどこからでも呼び出す必要のある多くのオブジェクト(ロガー、dbなど)が存在する可能性があります。

この問題を解決するために、サービス(ロガーなど)へのすべての参照を内部的に保存する、いわゆる「オブジェクトマネージャー」(またはsymfonyのようなサービスコンテナー)を使用するように言われました。

しかし、サービスプロバイダーが純粋なシングルトンほど悪くないのはなぜですか?

サービスプロバイダーも依存関係を隠し、最初のインスタンスの作成をラップアウトするだけです。そのため、シングルトンの代わりにサービスプロバイダーを使用する理由を理解するのに本当に苦労しています。

PS。依存関係を隠さないようにするには、DIを使用する必要があることを知っています(Miskoが述べたように)

追加

私は付け加えます:最近のシングルトンはそれほど悪ではありません、PHPUnitの作成者はここでそれを説明しました:

DI +シングルトンは問題を解決します:

<?php
class Client {

    public function doSomething(Singleton $singleton = NULL){

        if ($singleton === NULL) {
            $singleton = Singleton::getInstance();
        }

        // ...
    }
}
?>

これですべての問題が解決されなくても、それはかなりスマートです。

DIとService Container以外に、このヘルパーオブジェクトにアクセスするための適切なソリューションはありますか?


2
@はいあなたの編集は誤った仮定をしています。セバスチャンは、コードスニペットがシングルオンを使用することで問題が少なくなることを決して示唆していません。これは、他の方法ではテストをよりテストしにくいコードを作成するための1つの方法にすぎません。しかし、それでも問題のあるコードです。実際、彼は明確に次のように述べています。正しい解決策は、シングルトンをまったく使用しないことです。
ゴードン、

3
@yesはSOLIDの原則に従います。
ゴードン、

19
シングルトンは悪いという主張に異議を唱えます。それらは誤用される可能性がありますが、どのツールで同様です。メスは命を救ったり、終わらせたりするのに使えます。チェーンソーは森林をきれいにして山火事を防ぐことができます。また、何をしているのかわからない場合は腕のかなりの部分を取り除くことができます。ツールを賢く使うことを学び、アドバイスを福音として扱わないでください。そのようにすると、思いもよらない心が生まれます。
paxdiablo

4
@paxdiablo彼らはある悪いです。シングルトンはSRP、OCP、DIPに違反しています。これらはアプリケーションにグローバルな状態と密結合を導入し、APIをその依存関係について嘘をつきます。これはすべて、コードの保守性、可読性、およびテスト容易性に悪影響を及ぼします。これらの欠点がわずかな利点を上回るまれなケースがあるかもしれませんが、99%ではシングルトンは必要ないと主張します。特にPHPでは、シングルトンはとにかくリクエストに対してのみ一意であり、ビルダーからコラボレーターグラフを組み立てるのは簡単です。
ゴードン、

5
いいえ、そうは思いません。ツールは機能を実行するための手段であり、通常は何らかの方法でより簡単にすることができますが、一部の(emacs?)はそれを難し​​くするというまれな区別があります:-)この場合、シングルトンはバランスツリーやコンパイラと同じです。オブジェクトのコピーを1つだけにする必要がある場合は、シングルトンがこれを行います。それがうまく機能するかどうかは議論の余地がありますが、まったく機能しないとは言えません。そして、チェーンソーがハンドソーより速い、または釘打ち機対ハンマーのようなより良い方法があるかもしれません。だからといって、ハンドソー/ハンマーが道具になりません。
paxdiablo

回答:


76

言うまでもなく、Service Locatorは2つの悪の小さい方です。これらの4つの違いへの「より少ない」沸騰(少なくとも、今のところ他のものを考えることはできません):

単一責任の原則

サービスコンテナは、シングルトンのように単一責任の原則に違反していません。シングルトンはオブジェクトの作成とビジネスロジックを混合しますが、サービスコンテナはアプリケーションのオブジェクトライフサイクルの管理に厳密に責任があります。その点では、サービスコンテナの方が優れています。

カップリング

シングルトンは通常、静的メソッド呼び出しのためにアプリケーションにハードコードされます。これにより、コードの依存関係密結合になり、モック化が困難になります。一方、SLは1つのクラスにすぎず、挿入できます。したがって、すべてのクラスはそれに依存しますが、少なくとも疎結合の依存関係です。したがって、ServiceLocatorをシングルトン自体として実装しない限り、それは多少優れており、テストも簡単です。

ただし、ServiceLocatorを使用するすべてのクラスは、結合の形式であるServiceLocatorにも依存するようになります。ServiceLocatorのインターフェースを使用することでこれを軽減できるため、具体的なServiceLocator実装にバインドされませんが、クラスは何らかの種類のLocatorの存在に依存しますが、ServiceLocatorをまったく使用しないと、再利用が大幅に増加します。

非表示の依存関係

ただし、依存関係を非表示にする問題は非常に深刻です。ロケーターを使用するクラスに挿入するだけでは、依存関係はわかりません。ただし、シングルトンとは対照的に、SLは通常、背後で必要なすべての依存関係をインスタンス化します。そのため、サービスをフェッチするとき、CreditCardの例ではMisko Heveryのようにはなりません。たとえば、依存関係のすべての依存関係を手動でインスタンス化する必要はありません。

インスタンス内から依存関係を取得することも、デメテルの法則に違反しており、コラボレーターを掘り下げるべきではないと述べています。インスタンスは直接の共同編集者とのみ会話する必要があります。これは、シングルトンとServiceLocatorの両方の問題です。

グローバルステート

テスト間で新しいService Locatorをインスタンス化すると、以前に作成されたすべてのインスタンスも削除されるため、グローバル状態の問題も多少緩和されます(間違いを犯してSLの静的属性に保存した場合を除く)。もちろん、SLが管理するクラスのグローバル状態には当てはまりません。

さらに詳しい説明については、サービスロケーターと依存性注入の Fowlerも参照してください。


シングルトンを使用するコードのテストに関する更新とSebastian Bergmannによるリンクされた記事に関するメモ:Sebastianは、提案された回避策がシングルオンの使用の問題を軽減することを決して示唆していません。これは、他の方法ではテストをよりテストしにくいコードを作成するための1つの方法にすぎません。しかし、それでも問題のあるコードです。実際、彼は明確に次のように述べています。


1
特に、ここではテスト容易性を強化する必要があります。静的メソッド呼び出しをモックすることはできません。ただし、コンストラクターまたはセッターを介して注入されたサービスをモックすることはできます。
David

44

サービスロケーターパターンはアンチパターンです。依存関係を公開する問題は解決しません(クラスの定義を見ても、依存関係が注入されていないため、サービスロケーターからヤンクされているため、依存関係はわかりません)。

それで、あなたの質問は、なぜサービスロケーターが良いのですか?私の答えは、そうではありません。

避ける、避ける、避ける。


6
インターフェースについて何も知らないようです。クラスはコンストラクタのシグネチャで必要なインターフェイスを説明するだけです-それは彼が知る必要があるすべてです。渡されたService Locatorはインターフェースを実装する必要があります、それだけです。IDEがインターフェイスの実装をチェックする場合、変更を制御するのは非常に簡単です。
OZ_

4
@ yes123:それは間違っていると言う人、そしてSLはアンチパターンなので間違っています。あなたの質問は「なぜSLが良いのか」です。私の答えは、そうではありません。
ジェイソン

5
SLがanit-patternであるかどうかについては議論しませんが、シングルトンやグローバルと比較した場合、悪の程度は非常に小さいと言います。シングルトンに依存するクラスをテストすることはできませんが、SLに依存するクラスをテストすることはできます(SLデザインが機能しなくなるまでねじ込むことができます)。

3
@Jasonインターフェースを実装するオブジェクトを渡す必要があります-それはあなたが知る必要があるものです。クラスコンストラクターの定義だけで制限し、コンストラクターに(インターフェイスではなく)すべてのクラスを記述したい-ばかげた考えです。必要なのはインターフェースだけです。このクラスをモックで正常にテストでき、コードを変更せずに動作を簡単に変更できます。追加の依存関係やカップリングはありません-これが(一般的に)依存性注入に必要なすべてです。
OZ_ 2011

2
確かに、データベース、ロガー、ディスク、テンプレート、キャッシュ、ユーザーを1つの「入力」オブジェクトにまとめるだけです。コンテナーを使用した場合よりも、オブジェクトがどの依存関係に依存しているかを簡単に確認できます。
Mahn

4

シングルトンパターンのように、サービスコンテナーは依存関係を非表示にします。依存関係注入コンテナを使用することをお勧めします。サービスコンテナにはすべての利点がありますが、サービスコンテナには(私の知る限り)欠点はありません。

私が理解している限り、2つの唯一の違いは、サービスコンテナーでは、サービスコンテナーが注入されるオブジェクトであるため(依存関係を非表示にする)、DICを使用すると、DICが適切な依存関係を注入します。DICによって管理されているクラスは、DICによって管理されているという事実を完全に認識していないため、結合が少なく、依存関係が明確で、ユニットテストが円滑になります。

これは、SOの両方の違いを説明する良い質問です。依存性注入とサービスロケータパターンの違いは何ですか?


「DICが適切な依存関係を挿入する」これはシングルトンでも起こりませんか?
ダイナミック

5
@ yes123-シングルトンを使用している場合、それを注入することはありません。ほとんどの場合、グローバルにアクセスするだけです(それがシングルトンのポイントです)。シングルトンを注入した場合、依存関係は隠されないが、シングルトンパターンの本来の目的が損なわれると言う場合は、このクラスにグローバルにアクセスする必要がないかどうかを自問してみてください。シングルトンにする必要がありますか?
rickchristie

2

Service Container内のオブジェクトを
1)継承(Object Managerクラスは継承でき、メソッドをオーバーライドできる)で簡単に置き換えることができるため、
2)構成の変更(Symfonyの場合)

そして、シングルトンは結合が高いためだけでなく、それらが_ シングル _tonであるために悪いです。ほとんどすべての種類のオブジェクトにとって、これは間違ったアーキテクチャです。

「純粋な」DI(コンストラクター内)を使用すると、非常に高額な料金が発生します。すべてのオブジェクトは、コンストラクターに渡される前に作成する必要があります。メモリの使用量が増え、パフォーマンスが低下します。また、常にオブジェクトを作成してコンストラクタに渡すだけではない-依存関係のチェーンを作成できる...私の英語はそれについて完全に議論するには十分ではありません。Symfonyのドキュメントで読んでください。


0

私は、単純な理由でグローバル定数やシングルトンを避けようとしています。APIを実行する必要がある場合があります。

たとえば、フロントエンドと管理者がいます。admin内では、ユーザーとしてログインできるようにしたいと考えています。admin内のコードを検討してください。

$frontend = new Frontend();
$frontend->auth->login($_GET['user']);
$frontend->redirect('/');

これにより、フロントエンドの初期化用に新しいデータベース接続、新しいロガーなどが確立され、ユーザーが実際に存在するか、有効かどうかが確認されます。また、適切な個別のCookieと位置情報サービスが使用されます。

シングルトンについての私の考えは、親の中に同じオブジェクトを2回追加することはできません。例えば

$logger1=$api->add('Logger');
$logger2=$api->add('Logger');

単一のインスタンスとそれを指す両方の変数を残します。

最後に、オブジェクト指向の開発を使用する場合は、クラスではなくオブジェクトを操作します。


1
あなたの方法は$api 、フレームワークの周りに変数を渡すことですか?私はあなたが何を言っているのか正確には理解できませんでした。また、呼び出しadd('Logger')が同じインスタンスを返す場合、基本的にはサービスコンテナがあります
動的な

はい、そのとおり。私はそれらを「システムコントローラ」と呼び、APIの機能を拡張するためのものです。同様に、「監査可能」コントローラをモデルに2回追加すると、まったく同じように機能します。インスタンスを1つだけ作成し、監査フィールドのセットを1つだけ作成します。
romaninsh
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.