游戏设计模式 #7 解耦型模式

作者:浅墨
2017-02-24
11 12 0

引言

本系列可在专题游戏设计模式查看。

解耦型模式 Decoupling Patterns

这一部分的三种模式,专注于解耦:

  • 组件模式将一个实体拆成多个,解耦不同的领域。
  • 事件队列解耦了两个互相通信的事物,稳定而且实时。
  • 服务定位器让代码使用服务而无需绑定到提供服务的代码上。

组件模式 Component

允许单一的实体跨越多个领域,无需这些领域彼此耦合。

要点
  • 组件模式:在单一实体跨越了多个领域时,为了保持领域之间相互解耦,可以将每部分代码放入各自的组件类中,将实体简化为组件的容器。
  • Unity引擎在设计中频繁使用了这种设计方法,从而让其易于使用。
使用场合

组件通常在定义游戏实体的核心部分中使用,当然,它们在其他地方也适用。这个模式在如下情况下可以很好的适用:

  • 有一个涉及了多个领域的类,而你想保持这些领域互相隔离。
  • 一个类正在变大而且越来越难以使用。
  • 想要能定义一系列分享不同能力的类,但是使用接口不足以得到足够的重用部分。
引申与参考
  • Unity核心架构中GameObject类完全根据此模式来进行设计。
  • 此模式与GOF设计模式中的策略模式类似。两种模式都是将对象的行为取出,委派到一个单独的从属对象中。两者的不同点在于:
  • 策略模式中分离出的策略对象通常是无状态的——它封装的是算法,而不是数据。策略模式定义了对象的行为,而不是该对象是什么。
  • 而组件模式就更加复杂。组件经常保存了对象的状态,这有助于确定其真正的身份。但是,其界限往往很模糊。有些情况下组件也许根本没有任何状态。在这种情况下,你可以在不同的容器对象中使用相同的组件实例。这样看来,它的行为确实更像一种策略。
  • 本节内容相关的英文原文
  • 本节内容相关的中文翻译

事件队列模式 Event Queue

事件队列模式,对消息或事件的发送与处理进行时间上的解耦。

要点
  • 事件队列:在先入先出的队列中存储一系列通知或请求。发送通知时,将请求放入队列并返回。处理请求的系统在稍晚些的时候从队列中获取请求并进行处理。 这样就解耦了发送者和接收者,既静态又及时。
  • 事件队列很复杂,会对游戏架构引起广泛影响。中心事件队列是一个全局变量。这个模式的通常方法是一个大的交换站,游戏中的每个部分都能将消息送过这里。
  • 事件队列是基础架构中很强大的存在,但有些时候强大并不代表好。事件队列模式将状态包裹在协议中,但是它还是全局的,仍然存在全局变量引发的一系列危险。
使用场合
  • 如果你只是想解耦接收者和发送者,像观察者模式和命令模式都可以用较小的复杂度来进行处理。在需要解耦某些实时的内容时才建议使用事件队列。
  • 不妨用推和拉来的情形来考虑。有一块代码A需要另一块代码B去做些事情。对A自然的处理方式是将请求推给B。同时,对B自然的处理方式是在B方便时将请求拉入。当一端有推模型另一端有拉模型时,你就需要在它们间放一个缓冲的区域。 这就是队列比简单的解耦模式多出来的那一部分。队列给了代码对拉取的控制权——接收者可以延迟处理,合并或者忽视请求。发送者能做的就是向队列发送请求然后就完事了,并不能决定什么时候发送的请求会受到处理。
  • 而当发送者需要一些回复反馈时,队列模式就不是一个好的选择。
引申与参考
  • 很大程度上,事件队列模式就是广为人知的GOF设计模式中观察者模式的异步实现。
  • 就像其他很多模式一样,事件队列有很多别名。其中一个是“消息队列”。 消息队列通常指代一个更高层次的实现。可以这样理解,事件队列在应用中进行交流,而消息队列通常在应用间进行交流。另一个别名是“发布/提交”,有时被缩写为“pubsub”,这个别名通常指代更大的分布式系统中的应用。
  • 在有限状态机与状态模式中,往往需要一个输入流。如果想要异步响应,可以考虑用队列模式来存储它们。
  • Go语言内建的“Channel”机制,其本质上就是事件队列。

服务定位模式 Service Locator

提供服务的全局接入点,而不必让用户和实现它的具体类耦合。

要点
  • 服务定位模式:服务类定义了一堆操作的抽象接口。具体的服务提供者实现这个接口。 分离的服务定位器提供了通过查询合适的提供者, 获取服务的方法,同时隐藏了提供者的具体细节和需要定位它的进程。
  • 一般通过使用单例或者静态类来实现服务定位模式,提供服务的全局接入点。
  • 服务定位模式可以看做是更加灵活,更加可配置的单例模式。如果用得好,它能以很小的运行时开销,换取很大的灵活性。相反,如果用得不好,它会带来单例模式的所有缺点以及更多的运行时开销。
  • 使用服务定位器的核心难点是它将依赖,也就是两块代码之间的一点耦合,推迟到运行时再连接。这有了更大的灵活度,但是代价是更难在阅读代码时理解其依赖的是什么。
使用场合
  • 服务定位模式在很多方面是单例模式的亲兄弟,在应用前应该考虑看看哪个更适合你的需求。
  • 让大量内容在程序的各处都能被访问时,就是在制造混乱。对何时使用服务定位模式的最简单的建议就是:尽量少用。
  • 与其使用全局机制让某些代码直接接触到它,不妨先考虑将对象传过来。因为这样可以明显地保持解耦,而且可以满足我们大部分的需求。当然,有时候不方便手动传入对象,也可以使用单例的方式。
引申与参考
  • Unity引擎在它的GetComponent()方法中使用了这个模式,协助组件模式的使用,方便随时获取到指定的组件。
  • 微软的XNA框架将这个模式内嵌到它的核心类Game中。每个实例有一个 GameServices 对象,能够用来注册和定位任何类型的服务。