web-dev-qa-db-ja.com

シングルトンパターンを使用すべきでないのはいつですか? (明白な以外に)

シングルトンを使用して、特定の状態またはサービスへのグローバルなアクセスポイントを提供することを望んでいます。シングルトンパターンの利点をこの質問で列挙する必要はありません。

私が興味を持っているのは、シングルトンが最初は良い選択に思えるかもしれませんが、あなたに噛み付くために戻ってくるかもしれない状況です。 SOの本やポスターの著者を何度も見たことがありますが、シングルトンパターンは非常に悪い考えであることが多いと言います。

ギャングオブフォーは、以下の場合にシングルトンを使用することを表明しています。

  • クラスのインスタンスが1つだけ存在し、既知のアクセスポイントからクライアントがアクセスできる必要があります。
  • 唯一のインスタンスがサブクラス化によって拡張可能であり、クライアントがコードを変更せずに拡張インスタンスを使用できる必要がある場合。

これらの点は確かに注目に値しますが、私が求める実用的な点ではありません。

本当に、本当にシングルトンを使用したいかどうかを評価するために使用する一連のルールまたは警告がありますか?

49
Mike

概要バージョン:

グローバルを使用する頻度を知っていますか? OK、シングルトンEVEN LESSを使用します。実際にははるかに少ない。ほとんどは決してない。それらは、グローバルが持つ隠されたカップリング(テスト性と保守性に直接影響を与える)とすべての問題を共有し、多くの場合、「1つしか存在できない」という制限は、実際には誤った仮定です。

詳細な回答:

シングルトンについて実現する最も重要なことは、それがグローバルな状態であることです。 グローバルに緩和されていないアクセスの単一インスタンスを公開するためのパターンです。これには、グローバルが持つプログラミングの問題のすべてがありますが、興味深い新しい実装の詳細を採用し、それ以外の場合は実際の価値はほとんどありません(または、実際には、単一インスタンスの側面では不必要な追加コストがかかる場合があります)。実装は非常に異なるため、実際には単なるファンシーな単一のインスタンスグローバルである場合に、オブジェクト指向のカプセル化メソッドと誤解されることがよくあります。

シングルトンを検討する必要がある唯一の状況は、すでにグローバルデータの複数のインスタンスが実際に論理エラーまたはハードウェアアクセスエラーになる場合です。それでも、通常はシングルトンを直接処理するのではなく、代わりにis必要な回数だけインスタンス化できるラッパーインターフェイスを提供しますが、グローバルステートにのみアクセスします。このようにして、引き続き依存性注入を使用できます。クラスの動作からグローバル状態を解除できない場合は、システム全体に大きな変化はありません。

ただし、これには微妙な問題があり、グローバルデータに依存していないように見えますが、依存しています。したがって、(シングルトンをラップするインターフェースの依存性注入を使用)は単なる提案であり、規則ではありません。一般に、クラスがシングルトンに依存していることがわかるので、クラスメンバー関数の腹の中で:: instance()関数を使用するだけでその依存関係が隠されるので、一般的にはなお良いです。また、グローバルな状態に依存するクラスを抽出して、それらをより良くすることができます単体テストそして、依存を焼いた場合にモックの何もしないオブジェクトを渡すことができますシングルトンを直接クラスに入れると、これははるかに困難になります。

シングルトンをベイクするとき:: instance呼び出しは、自分自身をクラスにインスタンス化します継承を不可能にします。回避策は通常、シングルトンの「単一インスタンス」部分を壊します。 NetworkManagerクラスの共有コードに依存する複数のプロジェクトがある状況を考えます。このNetworkManagerをグローバルな状態にして、単一のインスタンスにしたい場合でも、シングルトンにすることにはvery懐疑的である必要があります。自身をインスタンス化する単純なシングルトンを作成することにより、他のプロジェクトがそのクラスから派生することを基本的に不可能にします。

多くの人は ServiceLocator をアンチパターンであると考えていますが、シングルトンよりも半歩優れており、Go4パターンの目的を効果的に覆い隠しています。サービスロケーターを実装するには多くの方法がありますが、基本的な概念は、オブジェクトの構築とオブジェクトへのアクセスを2つのステップに分割することです。このようにして、実行時に適切な派生サービスに接続し、単一のグローバルな連絡先からそのサービスにアクセスできます。これには、明示的なオブジェクト構築順序の利点があり、ベースオブジェクトから派生させることもできます。これはまだ述べられているほとんどの理由で悪いですが、lessシングルトンよりも悪く、ドロップイン置換です。

許容可能なシングルトン(read:servicelocator)の具体的な例の1つは、SDL_mixerのような単一インスタンスのcスタイルのインターフェースをラップする場合です。シングルトンの多くの場合、単純に実装されるべきではありませんが、それはおそらくロギングクラスにありません(コンソールおよびディスクにログを記録したい場合、またはサブシステムを個別にログに記録したい場合)。

ただし、グローバルな状態に依存することの最も重要な問題は、適切な単体テストを実装しようとする場合、ほとんど常に発生します(そして、それを実行する必要があります) )。実際にアクセスできないクラスの腸が、軽減されていないディスクの書き込みと読み取り、ライブサーバーへの接続と実際のデータの送信、またはスピーカーからのサウンドのブラストを試みると、アプリケーションの処理が非常に困難になります。ウィリーニーリー。テスト計画の場合、依存関係注入を使用して何もしないクラスをモックアップし(クラスコンストラクターでそれを行う必要があることを確認できます)、すべてを占領する必要なくそれを指すことができるので、はるかに優れています。クラスが依存するグローバル状態。

関連リンク:

パターン使用vs出現

パターンはアイデアや用語として役立ちますが、残念ながら、実際にパターンが必要に応じて実装されている場合、人々はパターンを「使用する」必要性を感じているようです。多くの場合、シングルトンは、一般的に議論されているパターンであるという理由だけで、具体的に単純になっています。パターンを意識してシステムを設計しますが、パターンが存在するという理由だけで、それらを曲げるようにシステムを設計しないでください。これらは便利な概念ツールですが、ツールボックスですべてのツールを使用できるわけではないのと同じように、パターンで同じことを行うべきではありません。必要に応じて、多かれ少なかれそれらを使用してください。

単一インスタンスサービスロケータの例

#include <iostream>
#include <assert.h>

class Service {
public:
    static Service* Instance(){
        return _instance;
    }
    static Service* Connect(){
        assert(_instance == nullptr);
        _instance = new Service();
    }
    virtual ~Service(){}

    int GetData() const{
        return i;
    }
protected:
    Service(){}
    static Service* _instance;
    int i = 0;
};

class ServiceDerived : public Service {
public:
    static ServiceDerived* Instance(){
        return dynamic_cast<ServiceDerived*>(_instance);
    }
    static ServiceDerived* Connect(){
        assert(_instance == nullptr);
        _instance = new ServiceDerived();
    }
protected:
    ServiceDerived(){i = 10;}
};

Service* Service::_instance = nullptr;

int main() {
    //Swap which is Connected to test it out.
    Service::Connect();
    //ServiceDerived::Connect();
    std::cout << Service::Instance()->GetData() << "\n" << ((ServiceDerived::Instance())? ServiceDerived::Instance()->GetData() :-1);
    return 0;
}
68
M2tM

一言: testing

テスト容易性の特徴の1つは、クラスの疎結合です。これにより、単一のクラスを分離して完全にテストできます。 1つのクラスがシングルトンを使用する場合(そして、私が古典的なシングルトンについて話している場合、静的なgetInstance()メソッドを通じて独自の特異性を強制するもの)、シングルトンユーザーとシングルトンは密接に結合します。シングルトンをテストせずにユーザーをテストすることはできなくなりました。

シングルトンはテストするべき災害です。それらは静的なので、サブクラスでそれらをスタブすることはできません。それらはグローバルであるため、再コンパイルや手間のかかる作業なしに、それらが指す参照を簡単に変更することはできません。シングルトンを魔法のように使用するものはすべて、制御が困難なものへのグローバル参照を取得します。これにより、テストの範囲を制限することが難しくなります。

12
Paul Rubel

私が見たシングルトンの最大の間違いは、シングルユーザーシステム(たとえば、デスクトッププログラム)を設計していて、シングルトンを多くのこと(設定など)に使用していて、ウェブサイトのようにマルチユーザーになりたいということです。またはサービス。

これは、マルチスレッドプログラムで使用されたときに、内部静的バッファーを持つC関数に何が起こったかに似ています。

6
Lou Franco

シングルトンは絶対に避けてください。アプリケーションのスケーリングを制限します。実際に対処する問題を分析し、スケーラビリティについて考え、アプリケーションに必要なスケーラビリティに基づいて決定を下します。

結局のところ、シングルトンは、正しく設計されていない場合、リソースのボトルネックとして機能します。

場合によっては、これがアプリケーションに与える影響を完全に理解せずにこのボトルネックを導入することがあります。

シングルトンリソースにアクセスしようとしているがデッドロックに陥っているマルチスレッドアプリケーションを処理しているときに問題に遭遇しました。これが、シングルトンをできるだけ避けようとする理由です。

設計にシングルトンを導入する場合は、実行時の影響を理解し、いくつかの図を作成して、問題の原因となる可能性のある場所を特定してください。

5
WeNeedAnswers

シングルトンは、適切なアクセサーを使用して、実際に必要な場所に適切にカプセル化するのに煩わされないもののキャッチオールとしてよく使用されます。

最終結果は、最終的にシステム全体のすべてのstaticsを収集するtarballです。ここで、何人かの人々がOODコードでGlobalsというクラスを見たことはありませんか?ああ。

3
Steve Townsend

どのデザインでもそれを完全に避けることはできません。ただし、その使用には注意が必要です。それは多くのシナリオで神のオブジェクトになる可能性があり、そのため目的を無効にします。

このデザインパターンは、すべての問題ではなく一部の問題を解決するためのソリューションです。実際、すべてのデザインパターンで同じです。

0
zerodin

私は経験豊富なプログラマだとは思っていませんが、現在のところ、実際にはシングルトンは必要ないというのが私の意見です...はい、最初は(グローバルと同様に)作業する方が簡単に思えますが、その後 "oh my "別のインスタンスが必要になる瞬間。

インスタンスはいつでもパスまたはインジェクトでき​​ますが、シングルトンを使用する方がはるかに簡単または必要である状況は実際にはわかりません

すべてを拒否しても、コードのテスト容易性の問題が残っています

0
NoxArt