シングルトンパターンの代替


27

シングルトンパターンに関するさまざまな意見を読みました。一部の人は、すべてのコストで回避する必要があると主張し、他の人は、特定の状況で役立つ可能性があると主張しています。

シングルトンを使用する1つの状況は、特定のクラスAのオブジェクトを作成するためにファクトリー(たとえば、タイプFのオブジェクトf)が必要な場合です。ファクトリーは、いくつかの構成パラメーターを使用して一度作成され、その後、タイプAがインスタンス化されます。したがって、Aをインスタンス化するコードのすべての部分は、シングルトンfをフェッチし、新しいインスタンスを作成します。たとえば、

F& f                   = F::instance();
boost::shared_ptr<A> a = f.createA();

一般的な私のシナリオは

  1. 最適化の理由(複数のファクトリオブジェクトは必要ありません)または共通の状態を共有するためにクラスのインスタンスが1つだけ必要です(たとえば、ファクトリはAのインスタンスをいくつ作成できるかを知っています)
  2. コードのさまざまな場所でFのこのインスタンスfにアクセスする方法が必要です。

このパターンが良いか悪いかについての議論には興味がありませんが、シングルトンの使用を避けたい場合、他にどのようなパターンを使用できますか?

私が持っていたアイデアは、(1)レジストリからファクトリオブジェクトを取得するか、(2)プログラムの起動中のある時点でファクトリを作成し、パラメータとしてファクトリを渡すことでした。

ソリューション(1)では、レジストリ自体がシングルトンであるため、シングルトンを使用しないという問題を工場からレジストリに移行しました。

(2)の場合、ファクトリオブジェクトの元となる初期ソース(オブジェクト)が必要なので、別のシングルトン(ファクトリインスタンスを提供するオブジェクト)に再びフォールバックするのではないかと心配しています。このシングルトンのチェーンをたどると、他のすべてのシングルトン を直接または間接的に管理する1つのシングルトン(アプリケーション全体)に問題を減らすことができます。

この最後のオプション(他のすべての一意のオブジェクトを作成し、他のすべてのシングルトンを適切な場所に注入する1つの初期シングルトンを使用)は、許容できる解決策でしょうか?これは、シングルトンを使用しないことを勧めるときに暗黙的に提案される解決策ですか、それとも他の解決策は何ですか?

編集

私の質問の要点は一部の人によって誤解されていると思うので、ここにいくつかの情報があります。ここで説明するように、シングルトンという言葉 は、(a)単一インスタンスオブジェクトを持つクラスと、(b)そのようなオブジェクトの作成とアクセスに使用されるデザインパターンを示すことができます。

物事を明確にするために、(a)には ユニークオブジェクト、(b)にはシングルトンパターンという用語を使用します。だから、私はシングルトンパターンと依存性注入が何であるかを知っています(最近、私は作業中のコードからシングルトンパターンのインスタンスを削除するためにDIを多用しています)。

私のポイントは、オブジェクトグラフ全体がmainメソッドのスタックにある単一のオブジェクトからインスタンス化されない限り、シングルトンパターンを通じていくつかの一意のオブジェクトに常にアクセスする必要があるということです。

私の質問は、完全なオブジェクトグラフの作成と配線をメインメソッドに依存させること(たとえば、パターン自体を使用しない強力なDIフレームワークを使用すること)が、シングルトンパターンを使用しない唯一のソリューションかどうか です。


8
依存関係の注入
ファルコン

1
@ファルコン:依存性注入...何?DIはこの問題を解決するために何もしませんが、多くの場合、便利な方法で非表示にします。
pdr

@FalconはIoCコンテナですか?これにより、1行で依存関係を解決できます。例:Dependency.Resolve <IFoo>(); またはDependency.Resolve <IFoo>()。DoSomething();
CodeART

これはかなり古い質問であり、すでに回答済みです。:私はまだこのリンクするのが良いでしょうけれどもstackoverflow.com/questions/162042/...
TheSilverBullet

回答:


7

2番目のオプションは良い方法です-これは一種の依存性注入です。これは、シングルトンやグローバル変数を避けたい場合にプログラム全体で状態を共有するために使用されるパターンです。

何かがあなたのファクトリを作成しなければならないという事実を回避することはできません。その何かがたまたまアプリケーションである場合、そうである。重要な点は、あなたのファクトリはどのオブジェクトがそれを作成したかを気にするべきではなく、ファクトリを受け取るオブジェクトはファクトリがどこから来たのかに依存すべきではないということです。オブジェクトにアプリケーションシングルトンへのポインタを取得させて、ファクトリを要求しないでください。アプリケーションにファクトリを作成させ、それを必要とするオブジェクトに渡します。


17

シングルトンの目的は、特定のレルム内に存在できるインスタンスが1つだけであることを強制することです。これは、シングルトンの動作を強制する強い理由がある場合に、シングルトンが有用であることを意味します。しかし実際には、これはめったにありません。マルチプロセスとマルチスレッドは確かに「ユニーク」の意味を曖昧にします-それはマシンごと、プロセスごと、スレッドごと、リクエストごとに1つのインスタンスですか?また、シングルトンの実装は競合状態を処理しますか?

シングルトンの代わりに、次のいずれかを使用することを好みます。

  • 短命のローカルインスタンス、例えば工場の場合:典型的な工場のクラスには最小限の状態があり、目的を果たした後もそれらを存続させる本当の理由はありません。クラスの作成と削除のオーバーヘッドは、現実世界のすべてのシナリオの99%で心配することはありません
  • インスタンスをリソースマネージャーなどに渡します。リソースのロードは高価であり、メモリ内に保持する必要があるため、これらは長命でなければなりませんが、それ以上のインスタンスの作成を防ぐ理由はまったくありません。おそらく、数か月先に2番目のリソースマネージャーを配置するのが理にかなっています。

その理由は、シングルトンは偽装のグローバル状態であるため、アプリケーション全体に高度な結合が導入されることを意味します。コードのどの部分でも、最小限の労力でどこからでもシングルトンインスタンスを取得できます。ローカルオブジェクトを使用するか、インスタンスを渡すと、より多くの制御が可能になり、スコープを小さく保ち、依存関係を狭くすることができます。


3
これは非常に良い答えですが、なぜシングルトンパターンを使用すべき/使用すべきでないのかについて、私は本当に尋ねていませんでした。グローバルな状態で起こりうる問題を知っています。とにかく、質問のトピックは、どのようにユニークなオブジェクトを持ちながら、シングルトンパターンのない実装であるかでした。
ジョルジオ

3

ほとんどの人(あなたを含む)は、Singletonパターンが実際に何であるかを完全に誤解しています。シングルトンパターンは、クラスのインスタンスが1つだけ存在することを意味し、アプリケーション全体のコードがそのインスタンスへの参照を取得するための何らかのメカニズムがあります。

GoFブックでは、静的フィールドへの参照を返す静的ファクトリメソッドは、そのメカニズムがどのように見えるかを示す単なる例であり、重大な欠点があります。残念ながら、誰もが彼らの犬はそのメカニズムを理解し、それがシングルトンのすべてであると考えました。

あなたが引用する選択肢は、実際にはシングルトンであり、参照を取得するための異なるメカニズムを備えています。(2)コールスタックのルート近くの数か所だけで参照が必要でない限り、明らかに多くのパススルーが発生します。(1)粗雑な依存性注入フレームワークのように聞こえるので、なぜそれを使用しないのですか?

利点は、既存のDIフレームワークが柔軟で強力かつ十分にテストされていることです。シングルトンを管理するだけではありません。ただし、それらの機能を完全に使用するには、アプリケーションを特定の方法で構造化する場合に最適に機能しますが、これは常に可能ではありません:理想的には、DIフレームワークを介して取得され、すべての依存関係が一時的に取り込まれ、その後、実行されました。

編集: 最終的には、すべてがメインメソッドに依存します。しかし、あなたは正しい:グローバル/静的の使用を完全に回避する唯一の方法は、すべてをmainメソッドからセットアップすることです。DIは、メインメソッドが不透明で、サーバーとアプリケーションコードを基本的に構成するサーバー環境で最も一般的であり、サーバーコードによってインスタンス化されて呼び出されるコールバックで構成されることに注意してください。しかし、ほとんどのDIフレームワークでは、「特殊なケース」のために、SpringのApplicationContextなどの中央レジストリに直接アクセスすることもできます。

だから基本的に人々がこれまでに出てきた最高のものは、あなたが言及した2つの選択肢の巧妙な組み合わせです。


基本的なアイデアの依存関係の注入は、基本的に上記でスケッチした最初のアプローチ(レジストリを使用するなど)であり、最後に基本的なアイデアだということですか?
ジョルジオ

@Giorgio:DIには常にそのようなレジストリが含まれていますが、それを改善する主な点は、オブジェクトを必要とする場所でレジストリを照会する代わりに、上で書いたように、一時的に移入する中央オブジェクトがあることです。
マイケルボルグ

@Giorgio:具体例で理解するのは簡単です:Java EEアプリがあるとしましょう。フロントエンドロジックは、JSFマネージドBeanのアクションメソッドにあります。ユーザーがボタンを押すと、JSFコンテナは管理対象Beanのインスタンスを作成し、アクションメソッドを呼び出します。しかし、その前に、DIコンポーネントはManaged Beanクラスのフィールドを調べ、特定の注釈を持つフィールドには、DI構成に従ってServiceオブジェクトへの参照が設定され、それらのServiceオブジェクトには同じことが起こります。その後、アクションメソッドはサービスを呼び出すことができます。
マイケルボルグワード

一部の人々(あなたを含む)は質問を注意深く読み、実際に尋ねられていることを誤解しています。
ジョルジオ

1
@Giorgio:元の質問では、依存性注入にすでに慣れていることを示すものは何もありませんでした。更新された回答を参照してください。
マイケルボルグワード

3

いくつかの選択肢があります。

依存性注入

すべてのオブジェクトには、作成時に依存関係が渡されます。通常、フレームワークまたは少数のファクトリクラスのいずれかが、オブジェクトの作成と配線を担当します。

サービスレジストリ

すべてのオブジェクトには、サービスレジストリオブジェクトが渡されます。さまざまなサービスを提供するさまざまなオブジェクトを要求するメソッドを提供します。Androidフレームワークはこのパターンを使用します

ルートマネージャー

オブジェクトグラフのルートである単一のオブジェクトがあります。このオブジェクトのリンクをたどると、最終的に他のオブジェクトを見つけることができます。コードは次のようになります。

GetSomeManager()->GetRootManager()->GetAnotherManager()->DoActualThingICareAbout()

モックとは別に、シングルトンを使用するよりもDIにはどのような利点がありますか?これは非常に長い間私を悩ませてきた質問です。レジストリとルートマネージャーについては、シングルトンとして、またはDI経由で実装されていませんか?(実際には、IoCコンテナーレジストリですよね?)
コンラッドルドルフ

1
@ KonradRudolph、DIの場合、依存関係は明示的であり、オブジェクトは特定のリソースがどのように提供されるかについての仮定を行いません。シングルトンの場合、依存関係は暗黙的であり、使用するたびに、そのようなオブジェクトは1つしか存在しないと仮定します。
ウィンストン

@KonradRudolph、他のメソッドはシングルトンまたはDIを使用して実装されていますか?はい、いいえ。最終的に、オブジェクトをグローバル状態で渡すか、オブジェクトを保存する必要があります。しかし、これを行う方法には微妙な違いがあります。上記の3つのバージョンはすべて、グローバルステートの使用を避けています。ただし、エンドコードが参照を取得する方法は異なります。
ウィンストンイーバート

シングルトンがインターフェイスを実装している場合(またはそうでない場合でも)、シングルトンインスタンスをメソッドに渡し、Singleton::instanceクライアントコードのすべての場所を照会する場合、シングルトンの使用は単一オブジェクトを想定しません。
コンラッドルドルフ

@KonradRudolph、あなたは本当にハイブリッドシングルトン/ DIアプローチを使用しています。私の純粋主義者側は、あなたが純粋なDIアプローチに行くべきだと考えています。ただし、シングルトンの問題のほとんどは実際には当てはまりません。
ウィンストンイーバート

1

シングルトンのニーズを1つの関数に要約できる場合、単純なファクトリー関数を使用しないのはなぜですか?グローバル関数(おそらく、この例のクラスFの静的メソッド)は本質的にシングルトンであり、コンパイラとリンカーによって一意性が強制されます。

class Factory
{
public:
    static Object* createObject(...);
};

Object* obj = Factory::createObject(...);

確かに、これはシングルトンの操作を単一の関数呼び出しに減らすことができない場合に壊れますが、関連する関数の小さなセットでうまくいくかもしれません。

言われていることはすべて、あなたの質問の項目1と2は、あなたが本当に何かの1つが本当に欲しいことを明確にします。シングルトンパターンの定義に応じて、既にそれを使用しているか、非常に近い状態にあります。私はあなたがシングルトンであるか、少なくとも1つに非常に近いものでなければ、あなたは何かを持つことができるとは思いません。シングルトンの意味に近すぎます。

あなたが示唆するように、ある時点で何かの1つを持たなければならないので、おそらく問題は何かの単一のインスタンスを持っているのではなく、それの悪用を防ぐ(または少なくとも落胆または最小化する)対策を講じることです。「シングルトン」から可能な限り多くの状態をパラメータに移動することは、良いスタートです。インターフェイスをシングルトンに絞り込むことも役立ちます。そのような方法で悪用される機会が少なくなるためです。ヒープやファイルシステムなど、シングルトンを非常に堅牢にする必要がある場合があります。


4
個人的には、これはシングルトンパターンの単なる別の実装と考えています。この場合、シングルトンインスタンスはクラス自体です。具体的には、「実際の」シングルトンと同じ欠点がすべてあります。
ヨアヒムザウアー

@Randall Cook:あなたの答えが私の要点を捉えていると思います。私は、シングルトンが非常に悪いので、どんな場合でも避けるべきだと頻繁に読みました。私はこれに同意しますが、一方で、常に回避することは技術的に不可能だと思います。「何か」が必要な場合は、どこかで作成し、何らかの方法でアクセスする必要があります。
ジョルジオ

1

C ++やPythonのようなマルチパラダイム言語を使用している場合、シングルトンクラスに代わる方法の1つは、名前空間にラップされた関数/変数のセットです。

概念的に言えば、無料のグローバル変数、無料のグローバル関数、および情報隠蔽に使用される静的変数をすべて名前空間にラップしたC ++ファイルは、シングルトン「クラス」とほぼ同じ効果をもたらします。

継承が必要な場合にのみ分解されます。この方法のほうがよかったと思われるシングルトンをたくさん見ました。


0

シングルトンカプセル化

ケースシナリオ。アプリケーションはオブジェクト指向であり、さらにクラスがある場合でも、3つまたは4つの特定のシングルトンが必要です。

例の前(C ++のような擬似コード):

// network info
class Session {
  // ...
};

// current user connection info
class CurrentUser {
  // ...
};

// configuration upon user
class UserConfiguration {
  // ...
};

// configuration upon P.C.,
// example: monitor resolution
class TerminalConfiguration {
  // ...
};

1つの方法は、一部の(グローバル)シングルトンを部分的に削除することです。それらのすべてを、他のシングルトンをメンバーとして持つ一意のシングルトンにカプセル化します。

これにより、「いくつかのシングルトン、アプローチ、シングルトンなし、アプローチ」の代わりに、「すべてのシングルトンを1つのアプローチにカプセル化する」ことができます。

例の後(C ++のような擬似コード):

// network info
class NetworkInfoClass {
  // ...
};

// current user connection info
class CurrentUserClass {
  // ...
};

// configuration upon user
// favorite options menues
class UserConfigurationClass {
  // ...
};

// configuration upon P.C.,
// example: monitor resolution
class TerminalConfigurationClass {
  // ...
};

// this class is instantiated as a singleton
class SessionClass {
  public: NetworkInfoClass NetworkInfo;
  public: CurrentUserClass CurrentUser;
  public: UserConfigurationClass UserConfiguration;
  public: TerminalConfigurationClass TerminalConfiguration;

  public: static SessionClass Instance();
};

例は擬似コードに似ており、マイナーなバグや構文エラーを無視し、質問の解決策を検討してください。

使用するプログラミング言語は、シングルトンまたは非シングルトン実装の実装方法に影響する可能性があるため、考慮すべき他の事項もあります。

乾杯。


0

元の要件:

一般的な私のシナリオは

  1. 最適化の理由(複数のファクトリオブジェクトは不要)または共通状態の共有(たとえば、ファクトリはAのインスタンスをいくつ作成できるかを知っている)のいずれかのために、クラスのインスタンスを1つだけ必要とします。
  2. コードのさまざまな場所でFのこのインスタンスfにアクセスする方法が必要です。

シングルトンの定義(および後で参照するもの)に合わせないでください。GoF(私のバージョンは1995)の127ページから:

クラスにインスタンスが1つだけあることを確認し、そのクラスへのグローバルアクセスポイントを提供します。

必要なインスタンスが1つだけの場合、それ以上インスタンスを作成できます。

単一のグローバルにアクセス可能なインスタンスが必要な場合は、単一のグローバルにアクセス可能なインスタンスを作成します。あなたがするすべてのことに名前を付けたパターンを持つ必要はありません。そして、はい、単一のグローバルにアクセス可能なインスタンスは通常悪いです。彼らに名前を付けても悪くはなりません。

グローバルなアクセシビリティを回避するために、一般的な「インスタンスを作成してそれを渡す」または「2つのオブジェクトの所有者にそれらを接着させる」アプローチがうまく機能します。


0

IoCコンテナーを使用してはどうですか?いくつか考えてみると、次のような行になります。

Dependency.Resolve<IFoo>(); 

IFoo起動時に使用する実装を指定する必要がありますが、これは1回限りの構成であり、後で簡単に置き換えることができます。通常、解決されたインスタンスの有効期間は、IoCコンテナによって制御できます。

静的シングルトンvoidメソッドは、次のものに置き換えることができます。

Dependency.Resolve<IFoo>().DoSomething();

静的シングルトンゲッターメソッドは、次のものに置き換えることができます。

var result = Dependency.Resolve<IFoo>().GetFoo();

例は.NETに関連していますが、他の言語でも非常によく似たものを実現できると確信しています。

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