编者按
本文已于作者 @董宸 授权转载,原载于知乎,如需转载请务必联系原作者。
正文
在最初的时候,我在开发的时候并没有分层的概念,也没有所谓的复用代码,写过的项目代码也是用完之后就不会再碰,下次需要做新项目的时候就从头开始写。
并且整体的构架耦合也非常严重,代码可以用一坨一坨来形容。
后来做了更多的项目之后,发现有很多代码可以进行重用,例如资源加载、UI 管理、通用工具类等等。层级结构开始发生了一些变化。
我们会将很多代码抽出来,可以为各种不同的项目提供服务,而逻辑业务层则是每一个游戏都需要各自进行编写。逻辑业务层依赖于通用服务层,服务层通过事件系统来与普通逻辑业务层交互,而业务逻辑层之间也通过事件系统进行交互。关于事件系统可以参阅我以前的文章:在Unity中自定义一个自己的事件分发系统
但是这个层级设计依旧存在着较大的问题,例如底层服务之间相互引用,造成在单独取出某个模块的时候产生了很多问题,我们不得不将很多不需要的模块全部迁入到新的项目当中。特别是通用工具类以及资源加载模块,非常多的服务层模块与它们出现了耦合。在这方面,我曾经才用过几种不同的方法:
首先我尝试使用宏对模块之间进行分离。之前我也有一篇文章特别讨论过使用宏进行模块分离:在Unity中利用宏将不同的模块完全分离。但是我在后面的开发中发现,宏对代码而言不易管理,并且容易对开发者造成误解。
然后我则是尝试将代码的耦合直接完全进行分离,而将未实现的代码在别的模块中另外实现一份,但是特别是对于资源加载模块而言,这种冗余是不允许的,因为要保证每一个模块的加载方式都是相同的。
除了服务层模块耦合的问题以外,上层逻辑与 UI 的耦合也是一个大问题。在游戏开发当中 UI 交互占了游戏开发的很大一部分,如果逻辑与UI紧耦合,则会造成更换皮肤时大量返工的情况发生。
首先我们来说一下UI分层,为了应对 UI 逻辑紧耦合的情况,我们通常的做法是将逻辑与 UI 分离,常用的三大设计模式:MVC、MVP 以及 MVVM。其中较为知名框架的有 PureMVC、StangeIOC 还有 uFrame。我自己也针对自己的情况专门实现了一套MVVM的框架,将逻辑与 UI 分离。实际上是分成了三块:表现、逻辑、数据。想要具体了解其中内容的可以阅读我之前的文章: Unity 中搭建一套自己的 MVVM 框架。
层级图如下所示:
当然,其实UI表现层也是会依赖于通用服务层的,毕竟通用服务层要对所有能提供的底层功能负责。例如UI层级的管理,资源的加载,当然通用工具类更是少不了。
但是只要不涉及向上依赖旧不存在什么问题。
接下去我们要解决困扰已久的服务层模块耦合的问题。
这思想又来自于设计模式,也就是中介者模式,中介者模式是大量依赖的优良解决方案,包括事件系统也是构建在这一模式之上的。
在之前的架构之中,每个服务模块之间都直接对其他模块进行引用。而在这里我们将通用工具类放入了更下一层,我们称为公用基础类库,在公用基础类库中我们将我们所有模块种最通用的功能进行抽出,尽量保证每一个服务模块都只依赖于通用工具,首先这就保证了尽量少的冗余代码。
然后我们尽量将 UI 管理模块之间的通信功能使用接口进行抽出,这样我们就可以保证我们可以将被引用模块的实现替换。例如在某些情形下我需要使用细粒度资源加载,而另外一款游戏当中我需要采用粗粒度的加载方式,只要实现了函数接口我便可以保证模块之间的松解耦与引用。
我们要做的是将我们将会用的模块注册到模块管理模块当中,模块管理模块会帮助我们创建与销毁模块,并且帮助我们获取不同的模块。
在业务逻辑层,我们可以通过 ModuleManager.Get<类名>来获取完整的模块功能,而在服务层内部则尽量通过接口来进行模块之间的相互引用,例如 ModuleManager.Get<接口名>,以保证每一个模块能够进行轻易的添加、移除、替换。
到这里为止,我的分层设计也差不多了,但是事实上我们依旧可以抽出很多东西以提高代码的重用度。
例如我们可以将常用的 UI 控件单独抽出,这样我们就不需要每次自己重新编写控件了。
事实上很多公司也是这么做的。统一的 UI 标识有利于产品的标识度,也可以减少程序与美术的工作量,何乐而不为。
在帧同步或者状态同步的网络游戏中甚至需要将逻辑层与表现层(例如角色的行走表现、技能释放等等)也进行分离,以保证游戏的流畅运行等等。
构架的改进是永无止境的。而且世界上也不存在最好的框架,而只有最合适的框架。如果为了一个小游戏而在框架上面大费心思,可能真的是杀鸡用了牛刀了。
但是不容忽视的是代码重用的重要性,巧妙地将各个模块分离之后能够帮助我们更好地开发新的项目,让我们的积累能够更加有效地投入到新的项目当中去。
本人才疏学浅,如有疏漏,请多多指正。
哇,大大你写的真好啊!!!
@木又:最近看到好多权,李四罗权吗?
设计模式真的非常重要,我抽个时间再看看设计模式书吧