シングルトン/グローバルの代替


16

私はシングルトン/グローバルの落とし穴について何度も聞いていますが、なぜ彼らがそれほど頻繁に眉をひそめているのか理解しています。

私が理解していないのは、エレガントで乱雑な選択肢が何であるかということです。シングルトン/グローバルを使用する代わりに、オブジェクトを必要とするオブジェクトに到達するまで、オブジェクトをエンジンオブジェクトに何百万レベルも渡すことが常に必要になるようです。

たとえば、私のゲームでは、ゲームの起動時にいくつかのアセットをプリロードします。これらのアセットは、プレーヤーがメインメニューをナビゲートしてゲームに入るまでずっと使用されません。このデータをGameオブジェクトからScreenManagerオブジェクトに(実際には1つのScreenのみがこのデータを処理するという事実にもかかわらず)、次に適切なScreenオブジェクト、およびその他の場所に渡すことになっていますか?

混乱した依存関係の注入のためにグローバル状態データを交換しているように見えますが、子オブジェクトに渡す目的以外はデータを気にしないオブジェクトにデータを渡します。

これは、シングルトンが良いことなのでしょうか、それとも私が見逃しているエレガントな解決策がありますか?

回答:


16

シングルトンとグローバルを混同しないでください。通常、ある種のグローバル変数が必要ですが、シングルトンはグローバル変数の単なる置き換えではなく にC ++およびFQAの静的初期化順序の問題を回避する方法です。(他の言語では、グローバル変数や裸の関数の欠如など、さまざまな言語の欠陥を回避する方法です。)

シングルトンの代わりにグローバルポインターを使用し、それが必要になる前に(手動で)初期化されていることを確認できる場合は、関数呼び出しとブランチオーバーヘッド、オブジェクトに到達するためのラメ構文を回避し、実際にテストが必要な場合、または設計が変更されたために、クラスの2番目のインスタンス。

必要ないくつかのグローバル変数(オーディオ出力、開いているウィンドウのリスト、キーボードハンドラーなどの一般的な例)については、サービスロケーターパターンをお勧めします。異なる実装(例:実際のオーディオデバイスとヌルオーディオデバイス)を簡単に置き換えることができ、すべてのグローバルを1つの構造に収集して、名前空間の汚染を防ぎます。


+1。サービスロケーターパターンリンクに対する良い回答と感謝。これは非常に興味深い読み物です。
-bummzack

1

コードの一部を魔法のように一部のデータについて「知らない」/持たない場合は、何らかの方法で渡す必要があります。ただし、それは必ずしも引数のみを介して渡される必要があるという意味ではありません。

あなたの例の場合、アセットをロードして保存する何らかの種類の「AssetManager」がなく、それからScreenManagerにそれへの参照を与えるだけでよいでしょう(おそらく作成時)。その意味で、別のオブジェクトにラップされたアセットへの参照を渡します。必要に応じてリーフ関数に渡すのではなく、初期化時に一度渡すことができます。

今度は、AssetManagerは、あなたがただ1つだけ欲しいものであり、シングルトンでもあるかもしれません。落とし穴を理解し、特にそれらを回避するためのコード(シングルトンが複数のスレッドから同時にアクセスされ、ブロックする必要のある操作を行うたびにフォークで自分自身を刺すことを想定)を提供します。


1

ジェイソンDは絶対に正しいと思う-これは私がそれを処理する方法です。

ゲームにはAssetManagerのインスタンスがあります。これは、任意のアセットを名前で取得できるオブジェクトです。

ゲームで:

assetManager = new AssetManager();
screenManager = new ScreenManager();
screenManager.assetManager = assetManager;

ScreenManagerで:

screen = new Screen();
screen.assetManager = assetManager;

画面内:

myAsset = assetManager.getBitmp("lava.png");

これで、すべての画面から必要なアセットにアクセスできます。これは、グローバルまたはシングルトンを使用するよりも複雑でもクレイジーでもありません。また、衝突することなく同じアプリケーションで2つのゲームのインスタンスを実行するオプションがあります。私はかつて8つのミニゲームで構成されたゲームを作成しなければなりませんでしたが、すべて同じベースクラス/フレームワークを共有しています。この参照渡しスタイルを使用するには、すべてのグローバル/シングルトンをリファクタリングする必要がありましたが、振り返ることはありませんでした。グローバルにする必要があるのは、オーディオ、ネットワーク、I / Oなど、物理的に一度しか存在できないものだけです。


0

Factoryパターンを使用して、Singletonを置き換えることができます。その後、ファクトリクラスは、作成できるインスタンスの数を制御します。インスタンスは、複数のインスタンスが必要になったときに簡単に変更できますAssetManagerこの記事で述べたように

これにより、Singletonの柔軟性がすべて得られ、多くの問題が発生することはありません。


別の、かなり制限された可能性は、クラスを静的にすることです(これはAssetManagerには実行可能ではないと思いますが、静的クラスを持つ言語でのみ可能です)。ただし、継承/ポリモーフィズムが必要ない場合にのみ機能します。これは非常に柔軟性のないソリューションです。

静的メソッドは花崗岩と同じくらい柔軟です。使用するたびに、プログラムの一部を具体的にキャストしています。足が固まるのを見ていて、足がそこに詰まっていないことを確認してください。いつか、あなたは本当に、あの危険なPrintSpoolerクラスの別の実装が本当に必要であり、インターフェース、ファクトリー、および実装クラスのセットであるはずだったことに驚くでしょう。どっ!

これは静的メソッドに関するものですが、静的クラスにも適用できます。


「クラスを静的にする」とはどういう意味ですか?C ++には静的クラスはありません。静的メソッドのみがあるということですか?名前空間の代わりにクラスを持つのはなぜですか?

1
@ジョー:さて、私が理解したように、質問はC ++に焦点を合わせていません。C#またはJavaでは、静的クラスを作成できますが、私はそれらを参照しています。また、私が言ったように、静的クラスはほとんどの場合最適なソリューションではありませんが、まれにグローバルのように機能する可能性があります。
マイケルクレメント
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.