16_游戏编程模式ServiceLocator 服务定位

####简单说,就是某个系统作为一个服务,对全局系统可见.

Service Locator (服务定位)

```
//简单粗暴的代码, 使用声音系统

// Use a static class?
AudioSystem::playSound(VERY_LOUD_BANG);

// Or maybe a singleton?
AudioSystem::instance()->playSound(VERY_LOUD_BANG);
```

类比:
如果我换了电话号码,需要通知我所有联系人,我换了电话号码
如果有一个提供个人信息的服务, 我只需要更新我的个人信息, 别人就可以查到我最新的电话号码


### 模式定义
service类定义了抽象的操作借口, 一个具体的服务提供者实现这些接口.
service locator 找到合适的服务提供者,并且隐藏服务者具体类型和查找过程.


### 何时使用
某段程序 需要在任何时候,任何地方进行访问, 这样会带来麻烦, 
比如说单例模式, 服务定位也有同样的问题.

尽量少的使用! 带来灵活性的同时,性能会下降.

### 注意事项
1 保证服务能被定位到
2 定位是全局访问的. 但有些服务在某些特定场景下才能正常使用.

### Sample Code
```

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

// Service Provider

class ConsoleAudio : public Audio {}


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

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

private:
  static Audio* service_;
};

//游戏启动时
Audio *audio = Locator::getAudio();
audio->playSound(VERY_LOUD_BANG);

//使用定位
Audio *audio = Locator::getAudio();
audio->playSound(VERY_LOUD_BANG);


// 当所使用定位的时候, 服务提供还没有设置, 将会出错, 所谓我们提供一个默认的服务

class NullAudio: public Audio
{
public:
  virtual void playSound(int soundID) { /* Do nothing. */ }
  virtual void stopSound(int soundID) { /* Do nothing. */ }
  virtual void stopAllSounds()        { /* Do nothing. */ }
};


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

  static Audio& getAudio() { return *service_; }

  static void provide(Audio* service)
  {
    if (service == NULL)
    {
      // Revert to null service.
      service_ = &nullService_;
    }
    else
    {
      service_ = service;
    }
  }

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


```

### 装饰器模式

```
\\ 声音执行的log类

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 sounds");
    wrapped_.stopAllSounds();
  }

private:
  void log(const char* message)
  {
    // Code to log message...
  }

  Audio &wrapped_;
};


// 把服务提供者换成装饰之后的类
void enableAudioLogging()
{
  // Decorate the existing service.
  Audio *service = new LoggedAudio(Locator::getAudio());

  // Swap it in.
  Locator::provide(service);
}
```


### 设计判断

#### 服务如何定位

```
1 外部代码注册
    1. 快速简单
    2. 我们控制提供者的构建
    3. 可以在运行时改变服务
    4. 定位依赖于外部代码
2 编译期绑定
    1. 快速
    2. 保证服务可用
    3. 不能很容易的改变服务
3 运行时配置
    1. 改变服务不用重新编译
    2. 不用程序来修改服务(让策划配置)
    3. 可以提同时提供多个服务(不同的配置)
    4. 复杂
    5. 加载服务耗时(解析配置,等等)
```

#### 服务不能被定位到的时候
```
1 让用户处理
  1. 用户决定怎么面对错误
  2. 服务的用户必须处理错误
2 关闭游戏
  1. 用户不必处理丢失服务
  2. 如果服务找不到,就关闭游戏(容易debug, 但是会让使用这个服务的同事头疼)
3 返回一个null服务(默认服务)
  1. 用户不必处理丢失服务
  2. 游戏会继续运行,即使服务丢失(debug困难)
```

#### 服务的作用域
```
1 全局访问
  1. 服务提供者可控制, 禁止到处实例服务提供者
  2. 在哪使用和何时使用服务, 我们无法控制
2 受限访问
  1. 控制耦合, 把服务控制在一个继承树里, 系统里不耦合的部分还保持不耦合
  2. 导致重复. 比如不同的类都要保存一个服务类的引用.

恰当的设置服务的作用域, 比如说网络服务限制在online class里面访问, 而log系统则是全局的.
```


## see also
1 服务定位类似于单例, 根据需要不同选用合适的设计
2 unity 融合了服务定位与组件模式, GetComponent可以当作获取服务.
3 XNA 框架 Game 类的实例有一个GameServices对象, 可以用来注册, 定位任何类型的服务

 

posted @ 2015-01-21 15:09  summernight  阅读(326)  评论(0编辑  收藏  举报