ここでは、単一インスタンスとシングルトンデザインパターンを区別することが重要です。
単一インスタンスは現実にすぎません。ほとんどのアプリは、一度に1つの構成、一度に1つのUI、一度に1つのファイルシステムなどで動作するように設計されています。維持する状態やデータがたくさんある場合は、インスタンスを1つだけにして、可能な限り長く維持したいでしょう。
シングルトンデザインパターンは、非常に特殊なタイプの単一インスタンスであり、具体的には次のようなものです。
- グローバルな静的インスタンスフィールドを介してアクセス可能。
- プログラムの初期化時または最初のアクセス時に作成されます。
- パブリックコンストラクターはありません(直接インスタンス化できません)。
- 明示的に解放されることはありません(プログラムの終了時に暗黙的に解放されます)。
パターンがいくつかの潜在的な長期的な問題を引き起こすのは、この特定の設計選択のためです。
- 抽象クラスまたはインターフェイスクラスを使用できない。
- サブクラス化できない;
- アプリケーション全体での高い結合(変更が困難);
- テストが難しい(単体テストで偽造/モックができない);
- 可変状態の場合、並列化が困難です(広範なロックが必要です)。
- 等々。
これらの症状はいずれも、実際には単一インスタンスに固有のものではなく、シングルトンパターンのみです。
代わりに何ができますか?単純にシングルトンパターンを使用しないでください。
質問からの引用:
データの保存と同期を維持するアプリ内のこの場所を1つにして、サーバーからさまざまなサポートデータを繰り返し要求することなく、開いた新しい画面で必要なもののほとんどを照会できるようにするというアイデアでした。サーバーへの絶え間ないリクエストは帯域幅を使いすぎます-そして私は週に数千ドルの追加のインターネット請求書を話しているので、それは受け入れられませんでした。
この概念には名前がありますが、これはちょっとしたヒントですが、不確かに聞こえます。cacheと呼ばれます。おしゃれにしたい場合は、「オフラインキャッシュ」または単にリモートデータのオフラインコピーと呼ぶことができます。
キャッシュはシングルトンである必要はありません。複数のキャッシュインスタンスに対して同じデータをフェッチしないようにする場合は、単一のインスタンスである必要があります。しかし、それはあなたが実際にすべてにすべてをさらさなければならないという意味ではありません。
最初に行うことは、キャッシュの異なる機能領域を個別のインターフェイスに分離することです。たとえば、Microsoft Accessに基づいて世界最悪のYouTubeクローンを作成しているとします。
MSAccessCache
▲
|
+ ----------------- + ----------------- +
| | |
IMediaCache IProfileCache IPageCache
| | |
| | |
VideoPage MyAccountPage MostPopularPage
ここには、特定のクラスがアクセスする必要がある特定のタイプのデータ(メディア、ユーザープロファイル、静的ページ(フロントページなど))を記述するいくつかのインターフェイスがあります。これらはすべて1つのメガキャッシュによって実装されますが、代わりにインターフェイスを受け入れるように個々のクラスを設計するため、クラスがどのようなインスタンスを持っているかは気にしません。プログラムの開始時に物理インスタンスを1回初期化し、コンストラクターとパブリックプロパティを介してインスタンス(特定のインターフェイスタイプにキャスト)の受け渡しを開始します。
ちなみに、これはDependency Injectionと呼ばれます。一般クラスのデザインが、独自にインスタンス化したりグローバル状態を参照したりするのではなく、呼び出し元からの依存関係を受け入れる限り、Springや特別なIoCコンテナを使用する必要はありません。
なぜインターフェイスベースのデザインを使用する必要があるのですか?3つの理由:
コードを読みやすくします。インターフェイスから依存クラスが依存するデータを正確に理解できます。
Microsoft Accessがデータバックエンドに最適な選択肢ではないことに気付いた場合、それをより良いものに置き換えることができます。たとえば、SQL Serverを考えてみましょう。
特に 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などの名前を付けます。