回答:
シングルトンに対する2つの主な批判は、私が観察したことから2つの陣営に分類されます。
これらの両方の結果として、一般的なアプローチは、これらのクラスの単一インスタンスを保持するために幅広いコンテナオブジェクトを作成し、コンテナオブジェクトのみがこれらのタイプのクラスを変更する一方で、他の多くのクラスがコンテナオブジェクト。
私はそれがアンチパターンであることに同意します。どうして?それは、コードが依存関係について嘘をつくことを可能にし、以前の不変のシングルトンに可変状態を導入しないことを他のプログラマに信頼できないからです。
クラスには、文字列のみを受け取るコンストラクターがある場合があるため、分離してインスタンス化され、副作用はないと思います。ただし、静かに、ある種のパブリックでグローバルに利用可能なシングルトンオブジェクトと通信しているため、クラスをインスタンス化するたびに異なるデータが含まれます。これは、APIのユーザーだけでなく、コードのテスト容易性にとっても大きな問題です。コードを適切にユニットテストするには、シングルトンのグローバル状態を細かく管理し、認識して、一貫したテスト結果を取得する必要があります。
シングルトンパターンは、基本的に遅延初期化されたグローバル変数です。グローバル変数は、プログラムの外見上は無関係な部分の間の距離で不気味なアクションを許可するため、一般的かつ正当に悪と見なされます。ただし、IMHOでは、プログラムの初期化ルーチンの一部として(たとえば、構成ファイルまたはコマンドライン引数の読み取りによって)1箇所から一度設定され、その後定数として扱われるグローバル変数には何も問題はありません。このようなグローバル変数の使用は、コンパイル時に宣言された名前付き定数を持つことと精神的にではなく、文字のみが異なります。
同様に、Singletonsに対する私の意見は、プログラムの外見上は無関係な部分間で可変状態を渡すために使用される場合にのみ、それらは悪いということです。可変状態が含まれていない場合、または含まれる可変状態が完全にカプセル化されているため、オブジェクトのユーザーがマルチスレッド環境でもそれを知る必要がない場合、それらに問題はありません。
PHPの世界では多くのシングルトンを見てきました。パターンが正当であるとわかったユースケースを覚えていません。しかし、私は人々がなぜそれを使用したのかという動機についてアイデアを得たと思います。
単一インスタンス。
「アプリケーション全体でクラスCの単一インスタンスを使用します。」
これは、たとえば「デフォルトのデータベース接続」などの合理的な要件です。2番目のdb接続を作成しないという意味ではなく、通常はデフォルトのdb接続で作業することを意味します。
単一のインスタンス化。
「クラスCが複数回インスタンス化されることを許可しないでください(プロセスごと、要求ごとなど)。」
これは、クラスをインスタンス化すると、他のインスタンスと競合する副作用がある場合にのみ関係します。
多くの場合、これらの競合は、コンポーネントを再設計することで回避できます。たとえば、クラスコンストラクターから副作用を排除します。または、他の方法で解決できます。しかし、まだいくつかの合法的なユースケースがあるかもしれません。
また、「1つのみ」の要件が実際に「プロセスごとに1つ」を意味するかどうかを考慮する必要があります。たとえば、リソースの同時実行の場合、要件はむしろ「プロセス全体で1つ」であり、「プロセス全体で1つ」ではありません。また、他の場合はむしろ「アプリケーションコンテキスト」ごとであり、たまたまプロセスごとに1つのアプリケーションコンテキストがあります。
それ以外の場合、この仮定を強制する必要はありません。これを強制すると、単体テスト用に別のインスタンスを作成できなくなります。
グローバルアクセス。
これは、オブジェクトを使用する場所にオブジェクトを渡すための適切なインフラストラクチャがない場合にのみ正当です。これは、フレームワークや環境が悪いことを意味する可能性がありますが、それを修正する権限の範囲内ではない可能性があります。
価格は、密結合、隠された依存関係、およびグローバルな状態について悪いすべてです。しかし、あなたはおそらくすでにこれらに苦しんでいるでしょう。
遅延インスタンス化。
これはシングルトンの必須部分ではありませんが、それらを実装する最も一般的な方法のようです。しかし、遅延インスタンス化は良いことですが、それを実現するためにシングルトンは本当に必要ありません。
典型的な実装は、プライベートコンストラクターと静的インスタンス変数を備えたクラス、および遅延インスタンス化を備えた静的getInstance()メソッドです。
上記の問題に加えて、これは、クラスがすでに持っている他の責任に加えて、クラスが独自のインスタンス化とライフサイクルを制御するため、単一の責任原則に食いつきます。
多くの場合、シングルトンやグローバルステートなしで同じ結果を達成できます。代わりに、依存性注入を使用する必要があります。依存性注入コンテナを検討することをお勧めします。
ただし、次の有効な要件が残っているユースケースがあります。
したがって、この場合にできることは次のとおりです。
インスタンス化するクラスCをパブリックコンストラクターで作成します。
インスタンスにクラスCを使用する、静的インスタンス変数と遅延インスタンス化を持つ静的S :: getInstance()メソッドを使用して、別個のクラスSを作成します。
Cのコンストラクターからすべての副作用を取り除きます。代わりに、これらの副作用をS :: getInstance()メソッドに入れます。
上記の要件を持つクラスが複数ある場合、小さなローカルサービスコンテナでクラスインスタンスを管理し、コンテナにのみ静的インスタンスを使用することを検討できます。したがって、S :: getContainer()は遅延インスタンス化されたサービスコンテナーを提供し、コンテナーから他のオブジェクトを取得します。
可能な場合、静的なgetInstance()を呼び出さないでください。可能であれば、代わりに依存関係注入を使用してください。特に、互いに依存する複数のオブジェクトでコンテナアプローチを使用する場合、これらのどれもS :: getContainer()を呼び出す必要はありません。
オプションで、クラスCが実装するインターフェイスを作成し、これを使用してS :: getInstance()の戻り値を文書化します。
(まだこれをシングルトンと呼んでいますか?これをコメントセクションに残します。)
利点:
グローバル状態に触れることなく、単体テスト用にCの個別のインスタンスを作成できます。
インスタンス管理はクラス自体から分離されます->懸念の分離、単一責任の原則。
S :: getInstance()でインスタンスに別のクラスを使用すること、または使用するクラスを動的に決定することも非常に簡単です。
個人的には、問題の特定のクラスに1、2、3、または限られた量のオブジェクトが必要な場合にシングルトンを使用します。または、クラスの複数のインスタンスを作成してクラスが適切に機能することを望まないことをクラスのユーザーに伝えたい。
また、コードのほぼすべての場所で使用する必要がある場合にのみ使用し、それを必要とする各クラスまたは関数にパラメーターとしてオブジェクトを渡したくありません。
さらに、別の関数の参照透過性を壊さない場合にのみシングルトンを使用します。何らかの入力が与えられた場合、常に同じ出力が生成されます。すなわち、私はグローバルな状態にそれを使用しません。おそらく、そのグローバル状態は一度初期化され、変更されることはありません。
使用しない場合については、上記3を参照して、反対に変更してください。