LoginSignup
2
0

More than 5 years have passed since last update.

Game Programing Patterns #16 Service Locator

Last updated at Posted at 2017-04-25
1 / 19

どんなパターン?

  • サービスを利用するコードに対して、サービスが実装された具増クラスと結合することなしに、サービスへのグローバルなアクセスポイントを提供するパターン

パターンの要件

  1. サービスクラスはひとまとまりの操作を抽象インターフェースとして定義する
  2. 実際にサービスを提供する愚増クラスがこのインターフェースを実装する
  3. これらとは別にサービスロケータが適切な提供元を探し出して、サービスへのアクセスを提供する

パターンの適用条件

 前提として、シングルトンパターンのように、あらゆる箇所からアクセスできるものは問題であるため、使う場面を限定する必要がある

  1. ログ出力やメモリ管理のように、モジュールの公開APIにすべきでないものを利用する場合
  2. サウンド出力や、画面表示システムといった、本質的に1つしかありえない機能を利用する場合

使用上の注意

  1. サービスのありかを突き止めなければならない
    • サービスを必要とした時には必ず何らかのサービスを手に入られるように保証する
  2. サービス側は何によってありかを突き止められたわからない
    • 特定の文脈での使用しか想定されていないクラスの場合は、このパターンを使うことを避けたほうが安全

サンプルコード


サービス

 サービスは実装のない抽象インターフェースクラスであること

class Audio
{
public:
  virtual ~Audio() {}
  virtual void playSound(int soundID) = 0;
  virtual void stopSound(int soundID) = 0;
  virtual void stopAllSounds() = 0;
};

サービスプロバイダ

 サービスの実装部分

class ConsoleAudio : public Audio
{
public:
  virtual void playSound(int soundID)
  {
    // ゲーム機のサウンドAPIを使って音を出力する
  }

  virtual void stopSound(int soundID)
  {
    // ゲーム機のサウンドAPIを使って音の出力を停止する
  }

  virtual void stopAllSounds()
  {
    // ゲーム機のサウンドAPIを使って全ての音の出力を停止する
  }
};

ロケータ

 サービスとプロバイダの2つを繋ぐクラス

class Locator
{
public:
  static Audio* getAudio() { return service_; }

  static void provide(Audio* service)
  {
    service_ = service;
  }

private:
  static Audio* service_;
};

使い方

プロバイダ登録

ConsoleAudio *audio = new ConsoleAudio();
Locator::provide(audio);

呼び出し

Audio *audio = Locator::getAudio();
audio->playSound(VERY_LOUND_BANG);

NULLサービス

 ここまでだとプロバイダ登録前にサービスを使おうとすると、NULLが返されるという問題がある。
そこで、NULLオブジェクトと呼ばれる別のデザインパターンがあり、これを利用すると、オブジェクトを受け取った側は、あたかも「本物」のオブジェクトを受け取ったような安全に実行を継続できる。
そのパターンの導入のためにNULLサービスプロバイダを定義する。


サンプルコード


プロバイダ

 これだけ

class NullAudio: public Audio
{
public
  virtual void playSound(int soundID)
  virtual void stopSound(int soundID)
  virtual void stopAllSounds()
};

ロケータ

class Locator
{
public:
  static void initialize()
  {
    service_ = &nullService_;
  }

  static Audio* getAudio() { return service_; }

  static void provide(Audio* service)
  {
    // NULLサービスに戻す
    if (service == NULL) service = &nullService_;

    service_ = service;
  }

private:
  static Audio* service_;
  static NullAudio nullService_;
};

ログ・デコレータ

 サービスロケータを使ってできる別の改良策「デコレータ」
例として、ログの出力が挙げられる。


サンプルコード


プロバイダ

class LoggedAudio : public Audio
{
public:
  LoggedAudio(Audio &wrapped) : wrapped_(wrapped) {}

  virtual void playSound(int soundID)
  {
    log("play sound");
    wrapped_.playSound(soundID);
  }

  virtual void stopSound(int soundID)
  {
    log("stop sound");
    wrapped_.stopSound(soundID);
  }

  virtual void stopAllSounds()
  {
    log("stop all sougnds");
    wrapped_.stopAllSounds();
  }
}

private:
  Audio &wrapped_;

使い方

void enableAudioLogging()
{
// 既存のサービスを修飾(デコレート)する
  Audio *service = new LoggedAudio(
    Locator::getAudio()
  );

  // 新しいサービスに入れ替える
  Locator::provide(service);
}

まとめ・参考情報

  • サービスロケータはシングルトンと多くの点で似ているので、どちらの方がニーズに適しているのか比較検討するのが良い
  • Unityはこのパターンをコンポーネントパターンと組み合わせて、GetComponent() で使っている
2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0