《游戏编程模式》笔记——服务定位器
意图
提供服务的全局接入点,避免使用者和实现服务的具体类耦合。
模式
服务类定义了一堆操作的抽象接口。具体的服务提供者来实现这个接口。分离的服务定位器提供了通过查询获取服务的方法,同时隐藏了服务提供者的具体细节和定位它的过程。
何时使用
少用。
服务定位器是更加灵活,更加可配置的单例模式。用得好可以用很小的运行时开销,换取很大的灵活性。用得不好,会带来单例模式的所有缺点已经更多的运行时开销。
在需要功能可以全局访问时,并且实现类可能会运行时切换,可以使用该模式。
设计决策
服务是何时被定位的?
外部代码注册:
简单快捷。获取服务的函数简单的返回指针,这通常会被编译器内嵌,我们几乎没有付出性能损失就获得了很好的抽象层。
可以控制如何构建提供者。
可以在游戏运行时改变服务。
但是定位器会依赖外部代码。任何访问服务的代码必需假定在某处的代码已经注册过服务。如果没有做初始化,游戏可能会崩溃或者不工作。
在编译时绑定:
快速。所有工作在编译时完成,运行时无需完成任何工作,是最快的方案。
保证服务是可用的。编译时就进行了定位,可以保存游戏完成编译后,服务一定是可用的。
缺点是无法轻易改变服务。由于绑定是发生在编译时,任何时候想要改变服务,都要重新编译并重启游戏。
运行时设置:
使用反射在运行时实例化对象。
可以更换服务而无需重新编译。
非程序员也可以改变服务。
统一的代码库可以同时支持多种设置。
复杂。这个方案是重量级的,需要创建设置系统来实现外部定位服务。
加载服务需要时间,虽然缓存可以最小化消耗,但是首次使用服务时的消耗还是不可避免的。
如果服务不能被定位怎么办?
让使用者处理它:
让使用者决定如何掌控失败。如果定位器不能定义全面的政策应对所有的情况,就将失败传回去,让使用者决定什么是正确的回应。
挂起游戏:
使用断言挂起游戏。
使用者不必处理缺失的服务。
如果服务没有找到,游戏会挂起。这会强迫我们解决定位服务的漏洞,但被阻塞的所有人都得等待漏洞修复。
返回空服务:
使用者不必处理缺失的服务。保证了总是会返回可用的服务,简化了使用服务的代码。
如果服务不可用,游戏仍将继续。缺点是较难查找缺失服务的漏洞,空服务会导致游戏不会像期望的那样行动。需要配合一些日志来查找漏洞。
服务的范围有多大?
如果是全局可访问:
鼓励整个代码库使用同样的服务。大多数服务都被设计成单一的。整个代码库接触到相同的服务,可以避免代码因为不能获取“真正的”服务而到处实例化提供者。
但是失去了何时何地使用服务的控制权,全局化的代价是任何东西都能接触它。
如果接触被限制在某个类中:
控制了耦合,通过显式限制服务到继承树的一个分支上,应该解耦的系统保持了解耦。
缺点是可能导致重复的付出。如果一对无关的类需要接触服务,每个类都要拥有服务的引用,无论谁定位或注册服务,都要在这些类之间重复处理。
服务定位模式是单例的兄弟,根据需要使用更合适的一种。
Unity的GetComponent()方法中使用了这个模式,协调它的组件模式。
参考
《游戏编程模式》