それで、シングルトンは悪いのでしょうか?


554

最近、シングルトンの使用(および過剰使用)に関する問題について多くの議論がありました。私も私のキャリアの早い段階でそれらの人々の一人でした。現在、問題が何であるかを見ることができますが、良い選択肢を見つけることができない場合がまだ多くあります。反シングルトンの議論の多くは実際にそれを提供しません。

ここに私が関わった最近の主要なプロジェクトの実例があります:

アプリケーションは、頻繁に更新されないサーバー状態からの大量のデータを使用する多くの個別の画面とコンポーネントを持つシッククライアントでした。このデータは基本的にシングルトンの「マネージャー」オブジェクト、つまり恐ろしい「グローバル状態」にキャッシュされていました。データの保存と同期を維持するアプリ内のこの場所を1つにして、サーバーからさまざまなサポートデータを繰り返し要求することなく、開いた新しい画面で必要なもののほとんどを照会できるようにするというアイデアでした。サーバーへの絶え間ないリクエストは帯域幅を使いすぎます-そして私は週に数千ドルの追加のインターネット請求書を話しているので、それは受け入れられませんでした。

基本的にこの種のグローバルデータマネージャーキャッシュオブジェクトを持つ以外に、ここで適切と思われる他のアプローチはありますか?もちろん、このオブジェクトは「シングルトン」である必要はありませんが、概念的には「シングルトン」であることが理にかなっています。ここできれいな代替品は何ですか?


10
シングルトンの使用はどのような問題を解決すると思われますか?他の選択肢(静的クラスなど)よりも、その問題を解決するのにどのように優れていますか?
アノン。

14
@Anon:静的クラスを使用すると状況が改善されます。まだ密結合がありますか?
マーティンヨーク

5
@マーティン:私はそれを「より良い」ものにすることを提案していません。ほとんどの場合、シングルトンは問題を探すための解決策であることを提案しています。
アノン。

9
@Anon:違います。静的クラスは、インスタンス化を(ほとんど)制御できず、シングルトンよりもマルチスレッドをさらに困難にします(インスタンスだけでなく、個々のメソッドへのアクセスをシリアル化する必要があるため)。シングルトンは、少なくとも静的クラスではできないインターフェイスを実装することもできます。静的クラスには確かに利点がありますが、この場合、シングルトンは間違いなく2つの重大な悪の小さい方です。あらゆる可変状態を実装する静的クラスは、大きな点滅するネオンのようなものです。「警告:悪いデザインが先に!」符号。
アーロンノート

7
@Aaronaught:シングルトンへのアクセスを同期するだけの場合、同時実行性は壊れます。シングルトンオブジェクトをフェッチした直後にスレッドが中断され、別のスレッドがオンになり、競合状態が非難される可能性があります。ほとんどの場合、静的クラスの代わりにシングルトンを使用することは、警告サインを取り除き、問題を解決することを考えるだけです。
アノン。

回答:


809

ここでは、単一インスタンスシングルトンデザインパターンを区別することが重要です。

単一インスタンスは現実にすぎません。ほとんどのアプリは、一度に1つの構成、一度に1つのUI、一度に1つのファイルシステムなどで動作するように設計されています。維持する状態やデータがたくさんある場合は、インスタンスを1つだけにして、可能な限り長く維持したいでしょう。

シングルトンデザインパターンは、非常に特殊なタイプの単一インスタンスであり、具体的には次のようなものです。

  • グローバルな静的インスタンスフィールドを介してアクセス可能。
  • プログラムの初期化時または最初のアクセス時に作成されます。
  • パブリックコンストラクターはありません(直接インスタンス化できません)。
  • 明示的に解放されることはありません(プログラムの終了時に暗黙的に解放されます)。

パターンがいくつかの潜在的な長期的な問題を引き起こすのは、この特定の設計選択のためです。

  • 抽象クラスまたはインターフェイスクラスを使用できない。
  • サブクラス化できない;
  • アプリケーション全体での高い結合(変更が困難);
  • テストが難しい(単体テストで偽造/モックができない);
  • 可変状態の場合、並列化が困難です(広範なロックが必要です)。
  • 等々。

これらの症状はいずれも、実際には単一インスタンスに固有のものではなく、シングルトンパターンのみです。

代わりに何ができますか?単純にシングルトンパターンを使用しないでください。

質問からの引用:

データの保存と同期を維持するアプリ内のこの場所を1つにして、サーバーからさまざまなサポートデータを繰り返し要求することなく、開いた新しい画面で必要なもののほとんどを照会できるようにするというアイデアでした。サーバーへの絶え間ないリクエストは帯域幅を使いすぎます-そして私は週に数千ドルの追加のインターネット請求書を話しているので、それは受け入れられませんでした。

この概念には名前がありますが、これはちょっとしたヒントですが、不確かに聞こえます。cacheと呼ばれます。おしゃれにしたい場合は、「オフラインキャッシュ」または単にリモートデータのオフラインコピーと呼ぶことができます。

キャッシュはシングルトンである必要はありません。複数のキャッシュインスタンスに対して同じデータをフェッチしないようにする場合は、単一のインスタンスである必要あります。しかし、それはあなたが実際にすべてにすべてをさらさなければならないという意味ではありません。

最初に行うことは、キャッシュの異なる機能領域を個別のインターフェイスに分離することです。たとえば、Microsoft Accessに基づいて世界最悪のYouTubeクローンを作成しているとします。

                          MSAccessCache
                                ▲
                                |
              + ----------------- + ----------------- +
              | | |
         IMediaCache IProfileCache IPageCache
              | | |
              | | |
          VideoPage MyAccountPage MostPopularPage

ここには、特定のクラスがアクセスする必要がある特定のタイプのデータ(メディア、ユーザープロファイル、静的ページ(フロントページなど))を記述するいくつかのインターフェイスがあります。これらはすべて1つのメガキャッシュによって実装されますが、代わりにインターフェイスを受け入れるように個々のクラスを設計するため、クラスがどのようなインスタンスを持っているかは気にしません。プログラムの開始時に物理インスタンスを1回初期化し、コンストラクターとパブリックプロパティを介してインスタンス(特定のインターフェイスタイプにキャスト)の受け渡しを開始します。

ちなみに、これはDependency Injectionと呼ばれます。一般クラスのデザインが、独自インスタンス化したりグローバル状態を参照したりするのではなく、呼び出し元からの依存関係を受け入れる限り、Springや特別なIoCコンテナを使用する必要はありません。

なぜインターフェイスベースのデザインを使用する必要があるのですか?3つの理由:

  1. コードを読みやすくします。インターフェイスから依存クラスが依存するデータを正確に理解できます。

  2. Microsoft Accessがデータバックエンドに最適な選択肢ではないことに気付いた場合、それをより良いものに置き換えることができます。たとえば、SQL Serverを考えてみましょう。

  3. 特に SQL Serverがメディアに最適な選択ではないことに気付いた場合は、システムの他の部分に影響を与えることなく実装を分割できます。そこから抽象化の真の力が生まれます。

さらに一歩進めたい場合は、Spring(Java)やUnity(.NET)などのIoCコンテナー(DIフレームワーク)を使用できます。ほとんどすべてのDIフレームワークは、独自のライフタイム管理を行い、特定のサービスを単一のインスタンスとして明確に定義することができます(多くの場合、「シングルトン」と呼ばれますが、これはよく知られているためです)。基本的に、これらのフレームワークは、インスタンスを手動でやり取りするという大部分の手間を省きますが、厳密に必要というわけではありません。 この設計を実装するために特別なツールは必要ありません。

完全を期すために、上記の設計も実際には理想的ではないことを指摘する必要があります。キャッシュを扱うとき(あなたと同じように)、実際には完全に別のレイヤーが必要です。言い換えれば、このようなデザイン:

                                                        +-IMediaRepository
                                                        |
                          キャッシュ(汎用)--------------- +-IProfileRepository
                                ▲|
                                | +-IPageRepository
              + ----------------- + ----------------- +
              | | |
         IMediaCache IProfileCache IPageCache
              | | |
              | | |
          VideoPage MyAccountPage MostPopularPage

この利点はCache、リファクタリングすることにした場合にインスタンスを分割する必要さえないことです。メディアの格納方法を変更するには、単にの代替実装を提供しIMediaRepositoryます。これがどのように適合するかを考えると、キャッシュの物理インスタンスを1つしか作成しないので、同じデータを2回取得する必要はありません。

これはいずれも、世界中のすべてのソフトウェアを、これらの高い凝集度と疎結合の厳密な標準に合わせて設計する必要があると言うことではありません。プロジェクトの規模と範囲、チーム、予算、期限などに依存します。しかし、最適な設計(シングルトンの代わりに使用する)を尋ねる場合、これがそれです。

PS他の人が述べたように、依存クラスがキャッシュを使用していることを認識することはおそらく最善の考えではありません-それは単に気にするべきではない実装の詳細です。そうは言っても、全体的なアーキテクチャは上記の図と非常によく似ています。個々のインターフェイスをCachesとは呼ばないでしょう。代わりに、Servicesなどの名前を付けます


131
私が読んだ最初の投稿は、実際にグローバルな状態に代わるものとしてDIを説明しています。これに費やした時間と労力に感謝します。この投稿の結果、私たちは皆元気です。
MrLane

4
キャッシュをシングルトンにできないのはなぜですか?それを渡し、依存性注入を使用する場合、シングルトンではありませんか?シングルトンは、適切にアクセスする方法ではなく、1つのインスタンスに限定するだけです。これに関する私の見解を
Erik Engheim

29
@AdamSmith:あなたは実際に読みました任意のこの答えのか?あなたの質問は最初の2つの段落で答えられます。シングルトンパターン!==単一インスタンス。
アーロンノート

5
@Cawas and Adam Smith-あなたのリンクを読んで、あなたが本当にこの答えを読んでいないように感じます-すべてがすでにそこにあります。
ウィルバート

19
@Cawasこの答えの要点は、シングルインスタンスとシングルトンの区別だと思います。シングルトンは悪いですが、シングルインスタンスはそうではありません。依存性注入は、シングルトンを使用せずにシングルインスタンスを使用するための優れた一般的な方法です。
ウィルバート

48

あなたが与えた場合、シングルトンの使用は問題ではなく、問題の症状 -より大きなアーキテクチャ上の問題のように聞こえます。

画面がキャッシュオブジェクトにデータを照会するのはなぜですか?キャッシュはクライアントに対して透過的でなければなりません。データを提供するための適切な抽象化が必要であり、その抽象化の実装ではキャッシュを利用できます。

この問題は、システムの部分間の依存関係が正しくセットアップされていない可能性が高く、これはおそらく全身的なものです。

画面にデータの取得場所の知識が必要なのはなぜですか?画面に、データの要求を満たすことができるオブジェクトが提供されていないのはなぜですか(キャッシュは隠されています)。多くの場合、画面を作成する責任は集中化されていないため、依存関係を注入する明確なポイントはありません。

繰り返しますが、大規模なアーキテクチャと設計の問題に注目しています。

また、オブジェクトのライフタイムは、オブジェクトの使用方法から完全に分離できることを理解することが非常に重要です。

キャッシュはアプリケーションの存続期間中(有効に)存続する必要があるため、オブジェクトの存続期間はシングルトンの存続期間になります。

しかし、Singletonの問題(少なくとも静的クラス/プロパティとしてのSingletonの一般的な実装)は、それを使用する他のクラスがそれを見つけることをどうやって行うかです。

静的なシングルトンの実装では、必要に応じて単純に使用するという規則があります。しかし、それは完全に依存関係を隠し、2つのクラスを密接に結び付けます。

クラスに依存関係を提供する場合、その依存関係は明示的であり、使用するクラスが知る必要があるのは、使用するコントラクトのみです。


2
特定の画面で必要になる可能性があるが、必ずしも必要ではない膨大な量のデータがあります。そして、これを定義するユーザーアクションが実行されるまでわかりません。多くの組み合わせがあります。そのため、クライアントでキャッシュおよび同期されたままの一般的なグローバルデータ(ほとんどがログイン時に取得)を使用し、その後のリクエストでキャッシュがさらに蓄積されます。これは、明示的にリクエストされたデータが同じセッション。サーバーへのリクエストを削減することに重点が置かれているため、クライアント側のキャッシュが必要です。<cont>
ボビーテーブル

1
<cont>基本的に透過的です。特定の必要なデータがまだキャッシュされていない場合、サーバーからのコールバックがあるという意味で。ただし、そのキャッシュマネージャーの実装(論理的および物理的)はシングルトンです。
ボビーテーブル

6
私はここでqstarinを使用しています。データにアクセスするオブジェクトは、データがキャッシュされていること(または実装の詳細)を認識してはなりません(または知る必要はありません)。データのユーザーは、単にデータを要求します(または、データを取得するためのインターフェースを要求します)。
マーティンヨーク

1
キャッシングは本質的に実装の詳細です。データを照会するインターフェースがあり、データを取得するオブジェクトは、それがキャッシュから来たかどうかを知りません。しかし、このキャッシュマネージャーの下にはシングルトンがあります。
ボビーテーブル

2
@Bobby Tables:あなたの状況は思ったほど悲惨ではありません。そのシングルトン(アプリが存在する限り存続するインスタンスを持つオブジェクトだけでなく、静的クラスを意味すると仮定)は依然として問題です。オブジェクトを提供するデータがキャッシュプロバイダーに依存しているという事実を隠しています。それが明示的で外部化されている場合は、より良い方法です。それらを分離します。それは本質的なあなたが簡単にコンポーネントを置き換えることができますテスト容易化、およびキャッシュプロバイダであるプライムなどの成分の一例(ASP.Netに裏打ちされたキャッシュ・プロバイダーがどのように多くの場合)。
クエンティン・スターリン

45

この質問について章全体を書きました。ほとんどの場合、ゲームのコンテキストで使用されますが、ほとんどはゲーム外で適用する必要があります。

tl; dr:

Gang of Fourシングルトンパターンは、2つのことを行います。どこからでもオブジェクトに簡単にアクセスできるようにし、そのインスタンスを1つだけ作成できるようにします。99%の時間、あなたが気にするのはその前半であり、それを得るために後半に沿ってカートすることは不必要な制限を追加します。

それだけでなく、便利なアクセスを提供するためのより良いソリューションがあります。オブジェクトをグローバルにすることは、それを解決するための核の選択肢であり、カプセル化を簡単に破壊する方法になります。グローバルについて悪いことはすべて、シングルトンに完全に適用されます。

同じオブジェクトに触れる必要があるコードの場所がたくさんあるという理由だけでそれを使用している場合は、コードベース全体に公開せずにそれらのオブジェクトのみに与えるより良い方法を見つけてください。その他の解決策:

  • 完全に捨てる。状態がなく、ヘルパー関数の単なる袋である多くのシングルトンクラスを見てきました。それらはインスタンスをまったく必要としません。それらを静的関数にするか、関数が引数として取るクラスのいずれかに移動します。あなたがMathできれば特別なクラスは必要ないでしょう123.Abs()

  • それを渡します。メソッドが他のオブジェクトを必要とする場合の単純な解決策は、単にそれを渡すことです。いくつかのオブジェクトを渡すことには何の問題もありません。

  • 基本クラスに入れてください。すべてが特別なオブジェクトにアクセスする必要がある多くのクラスがあり、それらが基本クラスを共有している場合、そのオブジェクトを基本のメンバーにすることができます。構築するときは、オブジェクトを渡します。派生オブジェクトは、必要なときにすべて取得できるようになりました。保護すると、オブジェクトがカプセル化されたままになります。


1
ここで要約できますか?
ニコール

1
完了しましたが、まだ章全体を読むことをお勧めします。
11

4
別の選択肢:依存性注入!
ブラッドキューピット

1
@BradCupit彼もリンクでそれについて話しています...私はまだすべてを消化しようとしていると言わなければなりません。しかし、それは私が今まで読んだシングルトンで最も解明された読書でした。これまでは、グローバル変数と同様に、シングルトンが必要であると確信していたため、Toolbox宣伝していました。今はもう知りません。サービスロケーターが単なる静的なツールボックスであるかどうかをmunificient氏に教えてもらえますか?シングルトン(つまり、ツールボックス)にした方が良いと思いませんか?
クレゴックス

1
「ツールボックス」は、私にとって「サービスロケーター」によく似ています。静的を使用するか、それ自体をシングルトンにするかは、ほとんどのプログラムにとってそれほど重要ではないと思います。静的なものに傾く傾向があるのは、なぜそうする必要がないのに、なぜ遅延初期化とヒープ割り当てを扱うのかということです。
顕著な

21

それ自体がグローバルな状態ではないことが問題です。

本当に心配する必要があるだけですglobal mutable state。定常状態は副作用の影響を受けないため、問題は少なくなります。

シングルトンの主な関心事は、結合を追加するため、テストなどの作業が難しくなることです。別のソース(ファクトリーなど)からシングルトンを取得することにより、結合を減らすことができます。これにより、特定のインスタンスからコードを切り離すことができます(ただし、ファクトリにさらに結合されるようになります(ただし、少なくとも、ファクトリは異なるフェーズの代替実装を持つことができます)。

あなたの状況では、シングルトンが実際にインターフェースを実装している限り(他の状況で代替手段を使用できるように)、それを回避できると思います。

しかし、シングルトンのもう1つの大きな欠点は、一度インプレースにすると、コードからそれらを削除し、他の何かに置き換えることが非常に難しいタスクになることです(再び結合することがあります)。

// Example from 5 minutes (con't be too critical)
class ServerFactory
{
    public:
        // By default return a RealServer
        ServerInterface& getServer();

        // Set a non default server:
        void setServer(ServerInterface& server);
};

class ServerInterface { /* define Interface */ };

class RealServer: public ServerInterface {}; // This is a singleton (potentially)

class TestServer: public ServerInterface {}; // This need not be.

それは理にかなっている。これにより、シングルトンを実際に悪用したことは決してなく、シングルトンの使用を疑い始めたと思うようになります。しかし、これらのポイントによると、私が行ったあからさまな虐待は考えられません。:)
ボビーテーブル

2
(おそらくそれ自体は「言うことではない」という意味です)
-nohat

4
@nohat:私は「クイーンズイングリッシュ」のネイティブスピーカーですle weekend。ありがとう:
マーティンヨーク

21
それ自体はラテン語です。
アノン。

2
@アノン:OK。それはそれほど悪くありません;
マーティンヨーク

19

じゃあ何?誰も言っていないので:Toolboxグローバル変数が必要場合です。

問題を別の角度から見ることで、シングルトンの悪用を回避できます。アプリケーションがクラスのインスタンスを1つだけ必要とし、アプリケーションが起動時にそのクラスを構成するとします。なぜクラス自体がシングルトンである必要があるのでしょうか?アプリケーションはこのような動作を必要とするため、アプリケーションがこの責任を負うことは非常に理にかなっています。コンポーネントではなく、アプリケーションがシングルトンでなければなりません。その後、アプリケーションは、アプリケーション固有のコードが使用できるように、コンポーネントのインスタンスを利用可能にします。アプリケーションがこのようなコンポーネントを複数使用する場合、それらを集約してツールボックスと呼ぶことができます。

簡単に言うと、アプリケーションのツールボックスは、それ自体を構成するか、アプリケーションの起動メカニズムが構成できるようにするシングルトンです...

public class Toolbox {
     private static Toolbox _instance; 

     public static Toolbox Instance {
         get {
             if (_instance == null) {
                 _instance = new Toolbox(); 
             }
             return _instance; 
         }
     }

     protected Toolbox() {
         Initialize(); 
     }

     protected void Initialize() {
         // Your code here
     }

     private MyComponent _myComponent; 

     public MyComponent MyComponent() {
         get {
             return _myComponent(); 
         }
     }
     ... 

     // Optional: standard extension allowing
     // runtime registration of global objects. 
     private Map components; 

     public Object GetComponent (String componentName) {
         return components.Get(componentName); 
     }

     public void RegisterComponent(String componentName, Object component) 
     {
         components.Put(componentName, component); 
     }

     public void DeregisterComponent(String componentName) {
         components.Remove(componentName); 
     }

}

しかし、何だと思いますか?シングルトンです!

そして、シングルトンとは何ですか?

そこから混乱が始まるかもしれません。

私にとって、シングルトンとは、常に単一のインスタンスのみを持つように強制されるオブジェクトです。インスタンス化することなく、いつでもどこでもアクセスできます。それがに非常に密接に関連してstaticいる理由です。比較のために、staticそれはインスタンスではないことを除いて、基本的に同じことです。自動的に割り当てられるため、インスタンス化する必要はありません。そして、それは問題を引き起こす可能性があり、実際にもたらします。

私の経験から、staticSingletonに置き換えるだけで、私が取り組んでいる中規模のパッチワークバッグプロジェクトの多くの問題を解決できました。それは、設計が不適切なプロジェクトに使用法があることを意味します。シングルトンパターン有用であるかどうかについては議論が多すぎると思いますが、実際にそれが本当に悪いのかどうかはあまり議論できません。しかし、一般に、静的メソッドよりもシングルトンを支持する良い議論があります。

シングルトンが悪いと確信している唯一のことは、良い方法を無視してそれらを使用するときです。それは確かに対処するのがそれほど簡単ではない何かです。しかし、悪い習慣はどのパターンにも適用できます。そして、私はそれを言うのは一般的すぎることを知っています...私はそれに対してあまりにも多くがあることを意味します。

誤解しないでください!

簡単に言えば、同じようにグローバルVARSシングルトンは、必要がある、まだすべての回で避けること。特に、過度に虐待されているためです。ただし、グローバル変数は常に回避できるわけではないため、最後のケースではそれらを使用する必要があります。

とにかく、ツールボックス以外にも多くの提案があり、ツールボックスと同様に、それぞれにアプリケーションがあります...

その他の選択肢

  • シングルトンについて読んだばかり最高の記事は、代替手段としてService Locatorを提案しています。私にとって、それは基本的に「静的ツールボックス」です。つまり、Service Locatorをシングルトンにし、ツールボックスを用意します。もちろん、シングルトンを回避するという当初の提案に反しますが、シングルトンの問題を強制するのは、それ自体のパターンではなく、シングルトンの使用方法です。

  • の選択肢として、工場パターンを提案します。これは同僚から聞いた最初の選択肢であり、グローバル変数として使用するためにすぐに削除しました。確かにその用途はありますが、シングルトンも同様です。

上記の選択肢は両方とも良い選択肢です。しかし、それはすべて使用法に依存します。

今、すべてのコストでシングルトンを回避する必要があることを意味するのは間違っています...

  • Aaronaughtの答えは、一連の理由からシングルトン決して使用ないことを示唆しています。しかし、それらはすべて、パターン自体に対する直接的なものではなく、悪用や悪用の方法に対する理由です。私はそれらの点についてのすべての心配に同意します、どうしてできないのですか?誤解を招くだけだと思います。

(抽象化またはサブクラス化する)能力は確かにありますが、それではどうでしょうか。それはそのためのものではありません。私が知る限り、インターフェースすることができません。高いカップリングも存在する可能性がありますが、それは単にそれが一般的に使用されているためです。する必要はありません。実際、結合自体はシングルトンパターンとは関係ありません。これが明確になったことで、テストの難易度もすでに排除されています。並列化の難しさについては、言語とプラットフォームに依存するため、やはりパターンの問題ではありません。

実用例

私はしばしば、シングルトンに対して賛成と反対の両方で2が使用されているのを見ます。Webキャッシュ(私の場合)とログサービス

一部の人は議論するでしょうが、ロギングは完璧なシングルトンの例です。

  • リクエスタには、ログにリクエストを送信するための既知のオブジェクトが必要です。これは、グローバルなアクセスポイントを意味します。
  • ロギングサービスは複数のリスナーが登録できる単一のイベントソースであるため、必要なインスタンスは1つだけです。
  • 異なるアプリケーションは異なる出力デバイスにログを記録する場合がありますが、リスナーを登録する方法は常に同じです。すべてのカスタマイズはリスナーを介して行われます。クライアントは、テキストがどのようにまたはどこに記録されるかを知らなくてもロギングを要求できます。したがって、すべてのアプリケーションはまったく同じ方法でロギングサービスを使用します。
  • すべてのアプリケーションは、ロギングサービスの1つのインスタンスだけで逃げることができなければなりません。
  • 再利用可能なコンポーネントを含むすべてのオブジェクトをロギングリクエスタにすることができるため、特定のアプリケーションに結合しないでください。

他の人は、ログサービスを実際に1つのインスタンスだけにすべきではないと最終的に理解すると、ログサービスを拡張することが難しくなると主張します。

まあ、私は両方の引数が有効であると言います。ここでも問題は、シングルトンパターンではありません。リファクタリングが実行可能なリスクであるかどうかは、アーキテクチャの決定と重み付けに基づいています。通常、リファクタリングが最後に必要な是正措置である場合、それはさらなる問題です。


@gnatありがとう!私は答えを編集してシングルトンの使用に関する警告を追加することを考えていました...あなたの引用は完璧にフィットします!
クレゴックス

2
気に入ってくれて嬉しいです。おそらく読者が疑問にレイアウトされ、具体的な問題に、この記事で作られたポイントを結ぶ苦労している、特にの光の中で-これはしかしdownvotesを避けるために役立つかどうかわからない素晴らしい分析前の答えで提供
ブヨ

@gnatええ、私はこれが長い戦いであることを知っていました。時間が経てばいいのですが。;-)
cregox

1
私はここでのあなたの一般的な方向性に同意しますが、極端にむき出しのIoCコンテナ(たとえばFunqよりもさらに基本的なもの)にすぎないように思われるライブラリについては、多分少し熱狂的です。Service Locatorは実際にはアンチパターンです。IoCコンテナを適切に使用するためにすべてをリファクタリングするにはコストが高すぎる「貧乏人のDI」とともに、主にレガシー/ブラウンフィールドプロジェクトで有用なツールです。
アーロンノート

1
@Cawas:ああ、ごめんなさい-と混同されましたjava.awt.Toolkit。私のポイントは同じですToolboxが、単一の目的を持つ一貫性のあるクラスというよりも、無関係な断片の断片のように聞こえます。私には良いデザインのように聞こえません。(参照する記事は、依存性注入とDIコンテナーが一般的になる前の2001年のものです。)
ジョンスキート

5

シングルトンデザインパターンに関する私の主な問題は、アプリケーションに適した単体テストを作成することが非常に難しいことです。

この「マネージャー」への依存関係を持つすべてのコンポーネントは、そのシングルトンインスタンスを照会することによってそうします。また、このようなコンポーネントの単体テストを作成する場合は、このシングルトンインスタンスにデータを注入する必要がありますが、これは簡単ではありません。

一方、「マネージャー」がコンストラクターパラメーターを介して依存コンポーネントに注入され、コンポーネントがマネージャーの具体的なタイプ、インターフェース、またはマネージャーが実装する抽象基本クラスのみを知らない場合、ユニットtestは、依存関係をテストするときに、マネージャーの代替実装を提供できます。

IOCコンテナーを使用してアプリケーションを構成するコンポーネントを構成およびインスタンス化する場合、IOCコンテナーを簡単に構成して「マネージャー」のインスタンスを1つだけ作成し、グローバルアプリケーションキャッシュを制御する同じ1つのインスタンスのみを実現できます。 。

ただし、単体テストを気にしない場合は、シングルトンのデザインパターンで十分です。(しかし、私はとにかくそれをしません)


きちんと説明すると、これはシングルトンのテストに関する問題を最もよく説明する答えです
ホセトマストチーノ

4

シングルトンは根本的に悪いことではありません。デザインコンピューティングは良いことも悪いこともあるという意味です。これは正しい場合(予想される結果を与える場合)またはそうでない場合があります。また、コードをより明確にしたり、より効率的にしたりする場合、役に立つ場合もあれば、そうでない場合もあります。

シングルトンが有用なケースの1つは、シングルトンが実際に一意のエンティティを表す場合です。ほとんどの環境では、データベースは一意であり、実際にはデータベースは1つしかありません。そのデータベースへの接続は、特別な許可が必要なため、またはいくつかの接続タイプを経由するため、複雑になる場合があります。この接続をシングルトンに整理することは、おそらくこの理由だけでも非常に理にかなっています。

ただし、シングルトンが実際にグローバル変数ではなく、シングルトンであることも確認する必要があります。これは、単一の一意のデータベースが実際に4つのデータベースであり、それぞれが実稼働、ステージング、開発、およびテストフィクスチャ用である場合に重要です。データベースシングルトンは、どのデータベースに接続する必要があるかを判断し、そのデータベースの単一のインスタンスを取得し、必要に応じて接続し、呼び出し元に返します。

シングルトンが実際にシングルトンではない場合(これはほとんどのプログラマーが動揺するときです)、遅延インスタンス化されたグローバルであり、正しいインスタンスを注入する機会はありません。

適切に設計されたシングルトンパターンのもう1つの便利な機能は、多くの場合観測できないことです。発信者が接続を要求します。それを提供するサービスは、プールされたオブジェクトを返すことができます。テストを実行している場合は、呼び出し元ごとに新しいオブジェクトを作成するか、代わりにモックオブジェクトを提供できます。


3

実際のオブジェクトを表すシングルトンパターンの使用は完全に受け入れられます。私はiPhone用に書いていますが、Cocoa Touchフレームワークには多くのシングルトンがあります。アプリケーション自体は、クラスのシングルトンで表されUIApplicationます。アプリケーションは1つしかないため、シングルトンで表すのが適切です。

シングルトンをデータマネージャークラスとして使用しても、適切に設計されていれば問題ありません。データプロパティのバケットである場合、グローバルスコープよりも優れています。ゲッターとセッターのセットである場合、それは優れていますが、それでも素晴らしいことではありません。おそらくリモートデータの取得、キャッシュ、セットアップ、およびティアダウンを含む、データへのすべてのインターフェイスを本当に管理するクラスである場合、それは非常に便利です。


2

シングルトンは、サービス指向アーキテクチャのプログラムへの単なる投影です。

APIは、プロトコルレベルでのシングルトンの例です。基本的にシングルトンであるものを介してTwitter、Googleなどにアクセスします。では、なぜプログラム内でシングルトンが悪くなるのですか?

それはあなたがプログラムをどう考えるかによる。プログラムを、ランダムにバインドされたキャッシュされたインスタンスではなくサービスの社会と考える場合、シングルトンは完全に理にかなっています。

シングルトンはサービスアクセスポイントです。おそらく非常に洗練された内部アーキテクチャを隠す機能の密結合ライブラリへのパブリックインターフェイス。

だから、私はシングルトンを工場とは違うものとは思わない。シングルトンには、コンストラクタパラメータを渡すことができます。たとえば、考えられるすべての選択メカニズムに対してデフォルトプリンタを解決する方法を知っているコンテキストによって作成できます。テストのために、独自のモックを挿入できます。そのため、非常に柔軟です。

キーは実行時にプログラムの内部にあり、サービスが起動して使用できる状態にあることを完全に確信してシングルトンにアクセスできる機能が少し必要です。これは、準備ができていると見なされるためにステートマシンを通過する必要があるプロセスで開始するさまざまなスレッドがある場合に重要です。

通常、XxxServiceクラスの周りにシングルトンをラップするクラスをラップしXxxます。シングルトンはクラスにXxxはまったくありませんXxxService。別のクラスに分離されています。これはXxx可能性は低いですが、複数のインスタンスを持つことができるためです。しかしXxx、各システムでグローバルにアクセス可能なインスタンスを1 つにしたいのです。XxxService関心事をうまく分離します。Xxxシングルトンポリシーを実施する必要はありませんが、Xxx必要なときにシングルトンとして使用できます。

何かのようなもの:

//XxxService.h:
/**
 * Provide singleton wrapper for Xxx object. This wrapper
 * can be autogenerated so is not made part of the object.
 */

#include "Xxx/Xxx.h"


class XxxService
{
    public:
    /**
     * Return a Xxx object as a singleton. The double check
     * singleton algorithm is used. A 0 return means there was
     * an error. Developers should use this as the access point to
     * get the Xxx object.
     *
     * <PRE>
     * @@ #include "Xxx/XxxService.h"
     * @@ Xxx* xxx= XxxService::Singleton();
     * <PRE>
     */

     static Xxx*     Singleton();

     private:
         static Mutex  mProtection;
};


//XxxService.cpp:

#include "Xxx/XxxService.h"                   // class implemented
#include "LockGuard.h"     

// CLASS SCOPE
//
Mutex XxxService::mProtection;

Xxx* XxxService::Singleton()
{
    static Xxx* singleton;  // the variable holding the singleton

    // First check to see if the singleton has been created.
    //
    if (singleton == 0)
    {
        // Block all but the first creator.
        //
        LockGuard lock(mProtection);

        // Check again just in case someone had created it
        // while we were blocked.
        //
        if (singleton == 0)
        {
            // Create the singleton Xxx object. It's assigned
            // to a temporary so other accessors don't see
            // the singleton as created before it really is.
            //
            Xxx* inprocess_singleton= new Xxx;

            // Move the singleton to state online so we know that is has
            // been created and it ready for use.
            //
            if (inprocess_singleton->MoveOnline())
            {
                LOG(0, "XxxService:Service: FAIL MoveOnline");
                return 0;
            }

            // Wait until the module says it's in online state.
            //
            if (inprocess_singleton->WaitTil(Module::MODULE_STATE_ONLINE))
            {
                LOG(0, "XxxService:Service: FAIL move to online");
                return 0;
            }

            // The singleton is created successfully so assign it.
            //
            singleton= inprocess_singleton;


        }// still not created
    }// not created

    // Return the created singleton.
    //
    return singleton;

}// Singleton  

1

最初の質問は、アプリケーションに多くのバグがありますか?キャッシュを更新するのを忘れているのか、キャッシュが悪いのか、変更が難しいのでしょうか?(色を変更しない限り、アプリはサイズを変更しないことを覚えています...ただし、色を元に戻してサイズを維持することはできます)。

あなたがすることは、そのクラスを持っているが、すべての静的メンバーを削除することです。OK、これはネサカリーではありませんが、お勧めします。実際には、通常のクラスのようにクラスを初期化し、ポインターを渡すだけです。

作業は多くなりますが、実際には混乱が少なくなります。もはやグローバルではないので、今はできないことを変えるべきではない場所もあります。私のマネージャークラスはすべて通常のクラスであり、そのように扱います。


1

IMO、あなたの例は大丈夫ですね。次のようにファクタリングすることをお勧めします。各(および各データオブジェクトの)キャッシュオブジェクト。キャッシュオブジェクトとdbアクセサーオブジェクトのインターフェイスは同じです。これにより、コード内外でキャッシュを交換できます。さらに、簡単な拡張ルートを提供します。

グラフィック:

DB
|
DB Accessor for OBJ A
| 
Cache for OBJ A
|
OBJ A Client requesting

DBアクセサーとキャッシュは、同じオブジェクトまたはアヒル型から継承して、同じオブジェクトのように見えます。プラグイン/コンパイル/テストできる限り、それはまだ機能します。

これにより物事が分離されるため、Uber-Cacheオブジェクトにアクセスして変更することなく新しいキャッシュを追加できます。YMMV。IANAL。等。


1

パーティーに少し遅れましたが、とにかく。

シングルトンは、他のツールと同様に、ツールボックス内のツールです。ツールボックスには、たった1本のハンマー以上のものがあればいいのですが。

このことを考慮:

public void DoSomething()
{
    MySingleton.Instance.Work();
}

public void DoSomething(MySingleton singleton)
{
    singleton.Work();
}
DoSomething(MySingleton.instance);

最初のケースは、高いカップリングなどにつながります。第2の方法では、@ Aaronaughtが説明している限り、問題はありません。その使い方のすべて。


同意しない。2番目の方法は「より良い」(結合の減少)ですが、DIによって解決される問題がまだあります。サービスの実装を提供するためにクラスのコンシューマーに依存しないでください。これは、クラスの作成時にコンストラクターで行う方が適切です。あなたのインターフェースは、引数に関して最低限必要なものだけを必要とします。また、クラスで操作するために単一のインスタンスが必要になる可能性も十分にあります。この場合も、このルールを実施するためにコンシューマーに依存することは危険であり、不要です。
AlexFoxGill

場合によっては、Diは与えられたタスクのやり過ぎです。サンプルコードのメソッドはコンストラクターである可能性がありますが、必ずしもそうではありません-具体的な例を見てはいけません。また、DoSomethingがISomethingを取得し、MySingletonがそのインターフェイスを実装することもできます。これはサンプルではなく、単なるサンプルです。
エフゲニー

1

各画面にコンストラクターでManagerを使用させます。

アプリを起動すると、マネージャーのインスタンスが1つ作成され、それが渡されます。

これは「制御の反転」と呼ばれ、構成の変更時およびテスト中にコントローラーを交換できます。さらに、アプリケーションの複数のインスタンスまたはアプリケーションの一部を並行して実行できます(テストに適しています!)。最後に、マネージャーは所有するオブジェクト(スタートアップクラス)で死にます。

したがって、アプリをツリーのように構成し、その上で使用されるすべてのものがそれらの下で使用されるようにします。メッシュのようなアプリを実装しないでください。メッシュは、誰もがすべての人を知っており、グローバルな方法でお互いを見つけます。

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