Game Programing Patterns #16 Service Locator

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

---

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

---

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

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

---

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

---

# サンプルコード

---

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

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

---

## サービスプロバイダ
　サービスの実装部分

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

---

## ロケータ
　サービスとプロバイダの２つを繋ぐクラス

```cpp
class Locator
{
public:
  static Audio* getAudio() { return service_; }
  
  static void provide(Audio* service)
  {
    service_ = service;
  }
  
private:
  static Audio* service_;
};
```

---

## 使い方
### プロバイダ登録
```c++
ConsoleAudio *audio = new ConsoleAudio();
Locator::provide(audio);
```

### 呼び出し
```c++
Audio *audio = Locator::getAudio();
audio->playSound(VERY_LOUND_BANG);
```

---

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

---

# サンプルコード

---

## プロバイダ
　これだけ

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

---

## ロケータ
```cpp
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_;
};
```

---

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

---

# サンプルコード

---

## プロバイダ
```cpp
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_;
```

---

## 使い方
```cpp
void enableAudioLogging()
{
// 既存のサービスを修飾（デコレート）する
  Audio *service = new LoggedAudio(
    Locator::getAudio()
  );
  
  // 新しいサービスに入れ替える
  Locator::provide(service);
}
```

---

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