背景
随着版本的开发迭代,游戏开发者难免会面对一些业务扩展维护方面的难题。
由于游戏业务具体开发周期短,需求灵活多变,开发量多,模块间逻辑关联度大,容易出bug 等特点,开发者往往很难同时兼顾开发效率和代码质量。而导致代码往糟糕趋势发展的原因,其中很重要的一点是,各系统模块间存在网状的调用关系,代码极易产生大量耦合,牵一发而动全身。这时候,如果项目中缺少一些统一的业务开发框架/模板/规则,不同风格的代码及处理流程整合在一起,维护起来就是非常痛苦的事情。此时,项目整体开发效率和质量只能取决于团队中每个成员自身的素质和追求。
如何避免网状的调用关系产生耦合,游戏业务中常见的处理方式,一般是忽略或者在小范围内改进优化。而在其他领域中,可能是引入某种设计模式。事件监听模型作为一种成熟的设计模式,在工业界拥有广泛的应用。它有很多别称,如中介者模式,发布订阅模式等。有的面向对象语言原生支持,有的则天然融合在框架服务中,成为服务的一种特性。它的本质是对观察者模式的扩展,通过预先注册(Register)并提供中转的方式,解离事件源和观察者之间的耦合,而它所擅长的事情,正是对网状调用关系解耦。
如何将事件监听模型合理有效的融入游戏业务的开发场景中,是本文探讨的主题。
设计目标
一个服务及对应的规则设计出来,最终是要给用户使用的, 如果脱离了实际应用场景,只会成为纸上谈兵,最终被人所遗忘。因此,在实现服务功能的同时,应尽可能兼顾开发效率,可读性,可扩展性等。 对使用者来说,simple is the best。
重新翻译一下游戏业务的痛点及对应需求:
痛点 | 需求 |
开发量大 | 应保证开发效率作为前提, 额外工作量应当尽可能的小, 不会对开发者产生过多负担。 |
需求灵活多变 | 服务提供的接口不会约束业务需求的灵活性,不会对需求扩展产生多余限制。 |
开发周期短 | 服务提供的接口和规则应当简单直观,额外的理解成本应尽可能少。 |
模块间关联度大 | 避免模块间产生耦合,保证模块内部的内聚性。 |
容易出bug | 服务应提供有限且简单的接口,避免给使用者埋坑。 |
从观察者模式说起
在介绍事件监听模式前,先简单介绍一下观察者模式。
传统的观察者模式分为观察者和主题两种角色, 观察者向主题注册, 主题向所有观察者推送事件。
一个网状调用关系的系统如下图:
经观察模式改进后:
观察者模式解决了主题和观察者的单向耦合。
但另一方面, 未解决观察者对主题的耦合(每个观察者仍需要知道主题的存在,才能完成注册)。
更严重的,开发者的工作量成倍提升, 从简单的接口调用 到 每个主题都需要维护一份观察者列表, 每个观察者都需要预先注册多个主题。
这也是为什么人人都知道观察者模式,但大多可行场景下,仍会弃之不用的直接原因。
出于以上两点不足,才有了解耦更彻底的事件监听模式。
事件监听模式在观察者和主题(事件源)两种角色的基础上, 引入了第三者:事件触发器。
事件触发器统一根据事件分类维护一个或多个观察者列表, 并往对应的观察者列表转发所有自己收到的事件。
另外,每个模块既可以是事件源,也可以是观察者。
作为观察者,往事件触发器预先注册需要监听的事件分类。
作为事件源,往事件触发器推送所有自己产生的事件。
由此, 观察者和事件源之间彻底解耦。
那么文章开头提到的网状调用关系在事件监听模型中是如何运转的呢?
当原本的调用关系是一对多时:多个观察者处理同一事件。
当原本的调用关系是多对一时:多个事件源推送同一事件。
如何定义事件
如何定义并分发事件,常见的做法大致分为三种:
- 多类事件采用统一的结构, 事件中包含事件类型和参数列表,参数含义由事件类型决定。