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对象, 可以用来注册, 定位任何类型的服务