Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
0
Help us understand the problem. What is going on with this article?
@kazuman519

Game Programing Patterns #16 Service Locator

More than 3 years have passed since last update.

Game Programing Patterns #16 Service Locator

by kazuman519
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() で使っている
0
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
kazuman519
お肉が好きです🍖
bm-sms
高齢社会に適した情報インフラを構築することで価値を創造し社会に貢献し続ける

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
0
Help us understand the problem. What is going on with this article?