Intent

“为某服务提供一个全局访问入口,以避免使用者与该服务的具体实现类之间产生耦合”。


Motivation

在游戏编程中,某些对象或者系统几乎出现在程序的每个角落。比如:内存分配、日志记录或随机数生成。我们通常认为类似这样的系统在整个游戏中是需要被随时访问的服务。

服务定位器,将一个服务的“是什么”(具体实现类型)和“在什么地方”(我们如何找到它的实现)这件事情解耦了。


When to Use It

每当你把东西变得全局可访问的时候,你就是在自找麻烦,这就是Singleton模式的主要问题。而Service Locator本质上并没有什么不一样的地方,因此使用的建议很简单:尽量别用。

针对环境属性可能的替代方案:通过参数传进去

  1. 简单易用,耦合直观,可以满足大部分需求。
  2. 但有时会显得毫无理由,或者舍不得代码难以阅读。比如有可能把一项环境属性连续传递了10层函数以便让一个底层的函数能够访问,为代码增加了毫无意义的复杂度。

Null service与Logging Decorator

当查找服务失败,或者想暂时禁用某个系统的时候,我们可以使用Null Object返回一个空服务提供器。比如,在开发过程中关闭音频可能会是一个很方便的功能。

在游戏开发过程中,一段有价值的日志可以很方便的帮助你估算出游戏引擎背后发生的事情。如果你在开发AI系统,你可能很乐于知道一个单位的AI状态什么时候发生了变化。如果你是音频程序员,你可能想要知道每次声音的播放记录,以便能够检测其是否能按正确的顺序触发。

典型的方案是调用一些log()函数,但这往往会导致另一个问题---我们现在有太多的日志了。Logging Decorator可以提供一个方案。


Problems

Service Locator用起来像一个更灵活、更可配置的Singleton,当被合理使用时,它能够让你的代码更有弹性,而且几乎没有什么运行时损失。相反,使用不当时,它会带来Singleton模式的所有缺点和糟糕的运行时开销。

在使用Singleton时,你可以放心的调用代码,因为它理所当然的在那里,但Service Locator不同,我们需要处理定位失败的情况。

Service Locator本质上仍然是一个Singleton,只不过它本身并不提供实质性的服务,它只是一个服务代理。这意味着这个服务可能在游戏中的任意代码在任意情况下使用,因此如果一个类只希望在特定上下文中被使用,那么最好避免使用这种模式以免将类服务暴露给全局环境。

依赖注入:外部代码负责为对象注入它所需求的依赖实例。

时序耦合:两份单独的代码必须按正确的顺序调用来保证程序正确工作。


References

  1. Singleton
  2. Service Locator

results matching ""

    No results matching ""