web-dev-qa-db-ja.com

シングルトンパターンの問題

私はここ数日シングルトンパターンについて読んでいます。一般的な認識では、それが必要とされるシナリオは(まれではないにしても)非常に少ないです。おそらく、次のような独自の一連の問題があるためです。

  • ガベージコレクション環境では、メモリ管理に関して問題になる可能性があります。
  • マルチスレッド環境では、ボトルネックが発生し、同期の問題が発生する可能性があります。
  • テストの見通しによる頭痛。

私はこれらの問題の背後にある考えを理解し始めていますが、これらの懸念について完全には確信していません。ガベージコレクションの問題の場合のように、シングルトン実装(パターンに固有)での静的な使用は、懸念事項ですか?それは静的インスタンスがアプリケーションまで続くことを意味するからです。それはメモリ管理を低下させるものですか(シングルトンパターンに割り当てられたメモリが解放されないことを意味します)?

もちろん、マルチスレッドセットアップでは、すべてのスレッドがシングルトンインスタンスに対して競合していることがボトルネックになります。しかし、このパターンを使用すると、どのように同期の問題が発生しますか(確かに、ミューテックスなどを使用してアクセスを同期できます)。

(ユニット?)テストの観点から、シングルトンは静的メソッド(モックやスタブが難しい)を使用するため、問題を引き起こす可能性があります。これについてはよくわかりません。誰かがこのテストの懸念について詳しく説明できますか?

ありがとう。

46
Ankur

ガベージコレクション環境では、メモリ管理に関して問題になる可能性があります

通常のシングルトンの実装では、シングルトンを作成すると、それを破棄することはできません。この非破壊的な性質は、シングルトンが小さい場合に受け入れられることがあります。ただし、シングルトンが大きい場合は、必要以上のメモリを不必要に使用しています。

ガベージコレクターは常にシングルトンが必要であると信じているため、これはガベージコレクター(Java、Pythonなど)がある言語では大きな問題です。 C++では、ポインタをdelete-することでチートできます。ただし、これはシングルトンであると想定されているため、独自のワームの缶を開きますが、それを削除することで、2番目のワームを作成できるようになります。

ほとんどの場合、このメモリの過剰使用はメモリパフォーマンスを低下させませんが、メモリリークと同じと見なすことができます。シングルトンが大きいと、ユーザーのコンピューターまたはデバイスのメモリが無駄になります。 (巨大なシングルトンを割り当てると、メモリの断片化に遭遇する可能性がありますが、これは通常は問題ではありません)。

マルチスレッド環境では、ボトルネックが発生し、同期の問題が発生する可能性があります。

すべてのスレッドが同じオブジェクトにアクセスしていて、ミューテックスを使用している場合、各スレッドは、別のスレッドがシングルトンのロックを解除するまで待機する必要があります。また、スレッドがシングルトンに大きく依存している場合、スレッドはその寿命のほとんどを待機に費やすため、パフォーマンスがシングルスレッド環境に低下します。

ただし、アプリケーションドメインで許可されている場合は、スレッドごとに1つのオブジェクトを作成できます。これにより、スレッドは待機に時間を費やさず、代わりに作業を行います。

テストの見通しによる頭痛。

特に、シングルトンのコンストラクターは1回しかテストできません。コンストラクターを再度テストするには、まったく新しいテストスイートを作成する必要があります。コンストラクターがパラメーターを受け取らない場合、これは問題ありませんが、パラメーターを受け入れると、ユニットティーストを有効にすることはできなくなります。

さらに、シングルトンを効果的にスタブ化することができず、モックオブジェクトの使用が困難になります(これを回避する方法はありますが、それだけの価値よりも厄介です)。これについてもっと読み続けてください...

(そしてそれは悪いデザインにもつながります!)

シングルトンは、設計が不十分であることの兆候でもあります。一部のプログラマーは、データベースクラスをシングルトンにしたい場合があります。 「私たちのアプリケーションは2つのデータベースを使用することは決してありません」と彼らは通常考えています。ただし、2つのデータベースを使用することが理にかなっている場合や、ユニットテストで2つの異なるSQLiteデータベースを使用する場合があります。シングルトンを使用した場合は、アプリケーションに重大な変更を加える必要があります。ただし、最初から通常のオブジェクトを使用している場合は、OOPを利用して、タスクを効率的かつ時間どおりに実行できます。

シングルトンのほとんどの場合は、プログラマーが怠惰である結果です。オブジェクト(データベースオブジェクトなど)を一連のメソッドに渡したくないため、各メソッドが暗黙的なパラメーターとして使用するシングルトンを作成します。しかし、このアプローチは上記の理由で噛み付きます。

可能であれば、シングルトンを使用しないようにしてください。最初から良いアプローチのように思えるかもしれませんが、通常は常に設計が不十分になり、コードを維持するのが困難になります。

38
carl

記事を見たことがない場合 シングルトンは病理学的嘘つきです 、あなたもそれを読むべきです。シングルトン間の相互接続がインターフェイスからどのように隠されているかについて説明しているため、ソフトウェアを構築するために必要な方法もインターフェイスから隠されています。

同じ著者によるシングルトンに関する他のいくつかの記事へのリンクがあります。

30
Greg Hewgill

シングルトンパターンを評価するときは、「代替手段は何ですか?シングルトンパターンを使用しなかった場合にも同じ問題が発生しますか?」と尋ねる必要があります。

ほとんどのシステムには、 大きなグローバルオブジェクト。これらは、大きくて高価なアイテム(たとえば、データベース接続マネージャー)、または広範な状態情報(たとえば、ロック情報)を保持するアイテムです。

シングルトンの代わりに、起動時にこのビッググローバルオブジェクトを作成し、このオブジェクトへのアクセスが必要なすべてのクラスまたはメソッドにパラメーターとして渡すこともできます。

シングルトン以外の場合にも同じ問題が発生しますか?それらを1つずつ調べてみましょう。

  • メモリ管理:ビッググローバルオブジェクトは、アプリケーションの起動時に存在し、シャットダウンするまで存在します。オブジェクトは1つしかないため、シングルトンの場合とまったく同じ量のメモリを使用します。メモリ使用量は問題ではありません。 (@MadKeithV:シャットダウン時の破棄の順序は別の問題です)。

  • マルチスレッドとボトルネック:すべてのスレッドは、このオブジェクトがパラメーターとして渡されたかどうかに関係なく、同じオブジェクトにアクセスする必要があります。 MyBigGlobalObject.GetInstance()。したがって、シングルトンであろうとなかろうと、同期の問題は同じです(幸いなことに標準ソリューションがあります)。これも問題ではありません。

  • ユニットテスト:シングルトンパターンを使用していない場合は、各テストの開始時にビッググローバルオブジェクトを作成できます。ガベージコレクターは、テストの完了時にそれを削除します。各テストは、前のテストの影響を受けない新しいクリーンな環境で開始されます。あるいは、シングルトンの場合、1つのオブジェクトがすべてのテストを通過し、簡単に「汚染」される可能性があります。そうです、シングルトンパターンは本当に かみ傷 ユニットテストに関しては。

私の好み:ユニットテストの問題だけのために、私はシングルトンパターンを避ける傾向があります。単体テストがない数少ない環境の1つである場合(たとえば、ユーザーインターフェイスレイヤー)、 かもしれない シングルトンを使用します。そうでない場合は避けます。

21
Andrew Shepherd

シングルトンに対する私の主な議論は、基本的に、シングルトンは2つの悪い特性を組み合わせているということです。

あなたが言及することは確かに問題になる可能性がありますが、そうではありません持っている。同期のことcan修正され、多くのスレッドがシングルトンに頻繁にアクセスする場合にのみボトルネックになります。これらの問題は厄介ですが、取引を妨げるものではありません。

シングルトンのはるかに根本的な問題は、彼らがやろうとしていることが根本的に悪いということです。

GoFで定義されているシングルトンには、次の2つのプロパティがあります。

  • グローバルにアクセス可能であり、
  • これにより、クラスがever複数回インスタンス化されるのを防ぎます。

最初のものは単純でなければなりません。一般的に言って、グローバルは悪いです。グローバルが必要ない場合は、シングルトンも必要ありません。

2番目の問題はそれほど明白ではありませんが、基本的には、存在しない問題を解決しようとします。

最後に偶然クラスをインスタンス化したのはいつでしたか?代わりに既存のインスタンスを再利用するつもりでしたか?

最後に誤って「std::ostream() << "hello world << std::endl」と入力したのはいつですか?意味 "std::cout << "hello world << std::endl "?

それは起こりません。したがって、そもそもこれを防ぐために必要はしません。

しかし、もっと重要なことは、「インスタンスは1つだけ存在しなければならない」という直感はほとんどの場合間違っています。私たちが通常意味するのは、「現在、1つのインスタンスの使用しか見ることができない」ということです。

ただし、「1つのインスタンスの使用しか確認できない」は、「誰かが2つのインスタンスを作成しようとすると、アプリケーションがクラッシュする」と同じではありません。

後者の場合、シングルトンが正当化される可能性があります。しかし前者では、それは本当に時期尚早の設計選択です。

通常、複数のインスタンスが必要になります。

多くの場合、複数のロガーが必要になります。クライアントが監視するために、クリーンで構造化されたメッセージを書き込むログと、自分で使用するためにデバッグデータをダンプするログがあります。

また、複数のデータベースを使用することになる可能性があることも容易に想像できます。

またはプログラム設定。もちろん、一度にアクティブにできる設定は1セットだけです。ただし、アクティブなときに、ユーザーは「オプション」ダイアログに入り、2番目の設定セットを構成する場合があります。彼はまだそれらを適用していませんが、彼が「OK」を押したら、それらを交換して、現在アクティブなセットを置き換える必要があります。つまり、彼が「OK」を押すまで、2セットのオプションが実際に存在するということです。

そして、より一般的には、ユニットテスト:

単体テストの基本的なルールの1つは、それらを分離して実行する必要があるということです。各テストでは、環境を最初からセットアップし、テストを実行して、すべてを破棄する必要があります。つまり、各テストでは、新しいシングルトンオブジェクトを作成し、それに対してテストを実行して、閉じます。

シングルトンは一度だけ作成されるため、これは明らかに不可能です。削除できません。新しいインスタンスを作成できません。

したがって、最終的に、シングルトンの問題は「スレッドセーフを正しく取得するのが難しい」などの技術ではなく、はるかに基本的な「実際にはコードにプラスの影響を与えません。2つの特性を追加します。それぞれそれらのネガティブ、あなたのコードベースに。誰がそれを望むだろうか?」

8
jalf

このユニットテストの懸念について。主な問題は、シングルトン自体のテストではなく、seオブジェクトのテストにあるようです。

このようなオブジェクトは、非表示で削除が難しいシングルトンに依存しているため、テスト用に分離することはできません。シングルトンが外部システム(DB接続、支払いプロセッサ、ICBM発射ユニット)へのインターフェースを表す場合、さらに悪化します。そのようなオブジェクトをテストすると、予期せずDBに書き込み、どこにいるのかを知っているお金を送ったり、大陸間ミサイルを発射したりする可能性があります。

3
Rafał Dowgird

私は、あなたがあちこちで議論を渡す必要がないようにそれらが頻繁に使用されるという以前の感情に同意します。私がやる。典型的な例は、システムロギングオブジェクトです。私は通常、それをシングルトンにするので、システム全体に渡す必要はありません。

調査-ロギングオブジェクトの例では、何人の人(手のショー)が何かをログに記録する必要があるかもしれないルーチンに余分な引数を追加しますか?vs-シングルトンを使用しますか?

1
RobertL

私は必ずしもシングルトンをグローバルと同一視するとは限りません。開発者がオブジェクトのインスタンスを、シングルトンまたはその他の方法で、空中から想起させるのではなく、パラメーターとして渡すことを妨げるものは何もありません。グローバルなアクセシビリティを非表示にする目的は、getInstance関数を少数の選択した友人に非表示にすることによっても実行できます。

ユニットテストの欠陥に関する限り、ユニットは小さいことを意味するので、ポイントを見逃していない限り、アプリを再度呼び出してシングルトンを別の方法でテストするのが妥当と思われます。

0
no-op