20

反シングルトンの解説を最初に見始めたとき、私は混乱しました。最近のいくつかのプロジェクトでシングルトンパターンを使用しましたが、それは見事に機能していました。実際、私はそれを何度も何度も使用してきました。

さて、いくつかの問題に遭遇した後、このSOの質問、特にこのブログ投稿を読んだ後、私は世界にもたらした悪を理解しました。

だから:既存のコードからシングルトンを削除するにはどうすればよいですか?

例:
小売店の管理プログラムでは、MVCパターンを使用しました。私のModelオブジェクトはストアを記述し、ユーザーインターフェイスはビューであり、2つの間のリエゾンとして機能する一連のコントローラーがあります。素晴らしい。ストアをシングルトンにしたことを除いて(アプリケーションは一度に1つのストアしか管理しないため)、ほとんどのコントローラークラスもシングルトンにした(1つのmainWindow、1つのmenuBar、1つのproductEditor ...)。これで、ほとんどのControllerクラスが次のような他のシングルトンにアクセスできるようになりました。

Store managedStore = Store::getInstance();
managedStore.doSomething();
managedStore.doSomethingElse();
//etc.

代わりに:

  1. 各オブジェクトのインスタンスを1つ作成し、それらにアクセスする必要があるすべてのオブジェクトに参照を渡しますか?
  2. グローバルを使用しますか?
  3. 他に何かありますか?

グローバルはまだ悪いでしょうが、少なくとも彼らはふりをしていません。

#1は、ひどく膨らんだコンストラクター呼び出しにすぐにつながることがわかります。

someVar = SomeControllerClass(managedStore, menuBar, editor, sasquatch, ...)

他の誰かがこれをもう経験しましたか?グローバルまたはシングルトンでなくても、共通の変数に多くの個別のクラスアクセスを与えるオブジェクト指向の方法は何ですか?

4

8 に答える 8

19

依存性注入はあなたの友達です。

優れたGoogleTestingブログのこれらの投稿をご覧ください。

うまくいけば、誰かがC ++の世界のためのDIフレームワーク/コンテナを作ったのですか?GoogleがC++テストフレームワークC++モックフレームワークをリリースしたようです。

于 2009-01-23T21:30:38.293 に答える
3

シングルトンを回避する私の方法は、「アプリケーショングローバル」は「VMグローバル」(つまり)を意味しないという考えに由来していますstatic。したがって、構成ストアのように、アプリケーショングローバルである必要があるApplicationContext以前のシングルトン情報を多く保持するクラスを紹介します。staticこのコンテキストはすべての構造に渡されます。IOCコンテナまたはサービスマネージャを使用している場合は、これを使用してコンテキストにアクセスできます。

于 2009-01-23T21:29:12.527 に答える
3

問題はシングルトン性ではありません。インスタンスが 1 つしか存在しないオブジェクトがあっても問題ありません。問題はグローバル アクセスです。Store を使用するクラスは、コンストラクターで Store インスタンスを受け取る (または設定可能な Store プロパティ/データ メンバーを持つ) 必要があり、それらはすべて同じインスタンスを受け取ることができます。ストアは、その中にロジックを保持して、インスタンスが 1 つだけ作成されるようにすることもできます。

于 2009-01-23T21:50:16.150 に答える
3

プログラムでグローバルまたはシングルトンを使用しても問題はありません。そのようながらくたについて独断的になる人を許さないでください. ルールとパターンは優れた経験則です。しかし、最終的にはあなたのプロジェクトであり、グローバル データが関係する状況をどのように処理するかについては、あなた自身の判断を下す必要があります。

グローバルを無制限に使用することは悪いニュースです。しかし、あなたが勤勉である限り、彼らがあなたのプロジェクトを台無しにすることはありません。システム内の一部のオブジェクトは、シングルトンに値します。標準入出力。あなたのログシステム。ゲームでは、グラフィックス、サウンド、入力サブシステム、およびゲーム エンティティのデータベース。GUI では、ウィンドウと主要なパネル コンポーネント。構成データ、プラグイン マネージャー、Web サーバー データ。これらはすべて、アプリケーションにとって多かれ少なかれ本質的にグローバルです。あなたの Store クラスもそれに合格すると思います。

グローバルを使用するコストは明らかです。アプリケーションの任意の部分が変更されている可能性があります。コードのすべての行が調査で疑わしい場合、バグを追跡することは困難です。

しかし、グローバルを使用しないことのコストはどうですか? プログラミングの他のすべてと同様に、それはトレードオフです。グローバルの使用を避けると、これらのステートフル オブジェクトを関数パラメーターとして渡す必要が生じます。または、それらをコンストラクターに渡して、メンバー変数として保存することもできます。そのようなオブジェクトが複数ある場合、状況は悪化します。現在、状態をスレッド化しています。場合によっては、これは問題ではありません。そのステートフルな Store オブジェクトを処理する必要がある関数が 2 つまたは 3 つだけであることがわかっている場合は、それがより適切なソリューションです。

しかし、実際には、常にそうであるとは限りません。アプリのすべての部分がストアに関係する場合、それを多数の関数にスレッド化することになります。その上、これらの関数の中には、複雑なビジネス ロジックを持つものもあります。そのビジネス ロジックをヘルパー関数で分割するときは、状態をもう少しスレッド化する必要があります。たとえば、深くネストされた関数には Store オブジェクトからの構成データが必要であることに気付いたとします。突然、ストア パラメーターを含めるために 3 つまたは 4 つの関数宣言を編集する必要があります。次に、戻って、ストアを実際のパラメーターとして、これらの関数のいずれかが呼び出されるすべての場所に追加する必要があります。関数がストアに対して持っている唯一の用途は、それを必要とするいくつかのサブ関数に渡すことかもしれません。

パターンは単なる経験則です。車で車線変更を行う前に、常に方向指示器を使用しますか? あなたが平均的な人なら、通常はルールに従いますが、午前4時に誰もいない高速道路を運転しているとしたら、誰ががらくたを言うでしょう? お尻を噛まれることもありますが、それは管理されたリスクです。

于 2009-05-05T16:51:38.960 に答える
2

インフレータブル コンストラクター呼び出しの問題に関しては、パラメーター クラスまたはファクトリ メソッドを導入して、この問題を活用できます。

パラメータ クラスは、パラメータ データの一部を独自のクラスに移動します。たとえば、次のようになります。

var parameterClass1 = new MenuParameter(menuBar, editor);
var parameterClass2 = new StuffParameters(sasquatch, ...);

var ctrl = new MyControllerClass(managedStore, parameterClass1, parameterClass2);

ただし、問題を別の場所に移動するだけです。代わりに、コンストラクターをハウスキープすることをお勧めします。問題のクラスを構築/開始するときに重要なパラメーターのみを保持し、残りは getter/setter メソッド (または .NET を実行している場合はプロパティ) で行います。

ファクトリ メソッドは、必要なクラスのすべてのインスタンスを作成するメソッドであり、前述のオブジェクトの作成をカプセル化するという利点があります。これらは、Singleton パターンで見られる getInstance メソッドに似ているため、Singleton からリファクタリングするのも非常に簡単です。次のスレッドセーフではない単純なシングルトンの例があるとします。

// The Rather Unfortunate Singleton Class
public class SingletonStore {
    private static SingletonStore _singleton
        = new MyUnfortunateSingleton();

    private SingletonStore() {
        // Do some privatised constructing in here...
    }

    public static SingletonStore getInstance() {
        return _singleton;
    }  

    // Some methods and stuff to be down here
}

// Usage: 
// var singleInstanceOfStore = SingletonStore.getInstance();

これをファクトリ メソッドにリファクタリングするのは簡単です。解決策は、静的参照を削除することです。

public class StoreWithFactory {

    public StoreWithFactory() {
        // If the constructor is private or public doesn't matter
        // unless you do TDD, in which you need to have a public 
        // constructor to create the object so you can test it.
    }

    // The method returning an instance of Singleton is now a
    // factory method. 
    public static StoreWithFactory getInstance() {
        return new StoreWithFactory(); 
    }
}

// Usage:
// var myStore = StoreWithFactory.getInstance();

使用法は同じですが、インスタンスが 1 つだけで行き詰まることはありません。当然、このファクトリ メソッドを独自のクラスに移動します。これは、Storeクラスがそれ自体の作成に関与してはならないためです (偶然にも、ファクトリ メソッドを移動した結果として、単一責任の原則に従います)。

ここからは多くの選択肢がありますが、それは自分の練習問題として残しておきます。ここでは、パターンを過度に設計 (または過熱) するのは簡単です。私のヒントは、必要な場合にのみパターンを適用することです。

于 2009-01-23T21:36:42.447 に答える
1

シングルトンパターンの使用を推奨せずに、必要に応じてシングルトンの使用を推奨したいと思います。単語の場合の違いに注意してください。シングルトン(小文字)は、何かのインスタンスが1つだけ必要な場合に使用されます。プログラムの開始時に作成され、それを必要とするクラスのコンストラクターに渡されます。

class Log
{
  void logmessage(...)
  { // do some stuff
  }
};

int main()
{
  Log log;

  // do some more stuff
}

class Database
{
  Log &_log;
  Database(Log &log) : _log(log) {}
  void Open(...)
  {
    _log.logmessage(whatever);
  }
};

シングルトンを使用すると、シングルトンアンチパターンのすべての機能が提供されますが、コードがより簡単に拡張可能になり、テスト可能になります(Googleテストブログで定義されている言葉の意味で)。たとえば、コードを大幅に変更することなく簡単に実行できるシングルトンを使用して、Webサービスにログオンする機能が必要になる場合もあります。

比較すると、シングルトンパターンはグローバル変数の別名です。本番コードでは使用されません。

于 2009-01-23T23:04:44.767 に答える
1

さて、まず第一に、「シングルトンは常に悪である」という概念は間違っています。複製できない、または複製できないリソースがある場合は常に、シングルトンを使用します。問題ない。

とは言うものの、あなたの例では、アプリケーションには明らかな自由度があります。誰かがやって来て、「でも、2つの店舗が欲しい」と言う可能性があります。

いくつかの解決策があります。まず最初に発生するのは、ファクトリクラスを構築することです。ストアを要求すると、ユニバーサル名(URIなど)で名前が付けられたストアが表示されます。そのストア内では、重要な領域または次の何らかの方法で、複数のコピーが互いに踏まないようにする必要があります。トランザクションのアトミック性を確保します。

于 2009-01-23T21:32:30.880 に答える
1

MiškoHeveryは、テスト容易性、特にシングルトンに関する素晴らしい記事シリーズを持っています。ここでは、問題についてだけでなく、問題を解決する方法についても説明しています(「欠陥の修正」を参照)。

于 2009-01-23T21:33:51.020 に答える