October 13, 2023

我对QFramework的浅显理解

在使用并学习了一段时间QFramework后,有了一定的认知,这里分享一些我自己目前对QFramework的一些认识和总结(不一定正确,欢迎指正)。

QFramework是一套分层的架构,何为分层?在我早期学习并制作Unity项目的时候,尝试使用Manager Of Managers的思路架构游戏程序,即一个Manager管理所有其他的Manager,每个Manager分别负责游戏各个模块的逻辑,例如SaveManager,InventoryManager,DialogueManger,UIManager,SceneManager,EventManager等,并将Manager设计为单例模式,将表现,逻辑和数据都写在里面,然后使用一个BaseManager统一管理这些Manager,我与团队的其他成员各自负责每个Manager的逻辑。最开始用来效果似乎还不错,但到项目越写越复杂的时候,会发现项目越来越难以维护。因为所有Manager几乎都在相互引用,在脑海里想像会让人感觉是在AnimatorController里面许多个动画相互连接的“蜘蛛网困境”,更要命的是,你有时候要实现自己所负责的Manager的一个功能时,需要改动其他人所负责的代码,因此在git合并时也常常造成冲突。

Manager Of Managers是架构吗?我认为是的,他将每个大功能大系统分开,设计为单例,又由一个中心统一管理,但这样的架构是松散的,他缺少限制导致其陷入“蜘蛛网困境”。QFramework引入分层的设计解决了这个问题,他将其系统架构分为四层:

摘自QFramework的Github主页

有了分层,我们就可以根据QFramework规则将我们散落到各处的Manager们有序的组织起来,我们可以将InventoryManager,DialogueManager,UIManager的逻辑写成InventorySystem,DialogueSystem,UISystem放在系统层,将相关的数据(如背包的数据,对话的数据)放在数据层,SaveSystemr负责数据的存储,放在工具层,而具体的表现的实现则放在表现层

QFramework里先实现了一个简单的IOC容器,仅仅能注册为单例,每次获取都为同一个实例,并用一个字典来维护.
QF中的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class IOCContainer
{
private Dictionary<Type, object> mInstances = new Dictionary<Type, object>();

public void Register<T>(T instance)
{
var key = typeof(T);

if (mInstances.ContainsKey(key))
{
mInstances[key] = instance;
}
else
{
mInstances.Add(key, instance);
}
}

public T Get<T>() where T : class
{
var key = typeof(T);

if (mInstances.TryGetValue(key, out var retInstance))
{
return retInstance as T;
}

return null;
}
}

然后给每个层都定义一个接口(IController ISystem IModel IUtility)在Architecture类中创建IOCContainer对象,分别定义每个层的注入和获取方法(例如RegisterSystem<T>()GetSystem<T>)通过对应的方法,将对应层的实例注入到IOCContainer中

使用QFramework时,我们需要定义一个架构类,让其继承的Architecture<T>类(类中声明了一个IOCContainer对象),一个游戏项目一般只需要一个Architecture,这便是我们的架构。我们的所有System Model Utility都需要在架构初始化时进行注入,反之我们在获取也需要先获取架构,从架构中获取对应的实例(QFramework定义了一个 IBelongToArchitecture接口,实现静态拓展使得我们可以很方便的使用this关键字获取Architecture)

接下来我们就可以使用Register注入实例,用Get获取实例。这样看来,好像只是把很多个单例放入了一个容器里面,似乎跟直接使用一堆Manager并没有区别,是的,因为我们还没有进行限制

我们可以写一个文档,整个项目分成哪些层,给每个层设定对应的规则,就像上文QFramework的主页写的各个层级的规则一样,让团队成员根据文档去遵守,这样我们就用文档架构的形式去规范整个程序,但是人是会犯错的,指不定团队里某个人就因为粗心没有遵守这样的规则,给项目埋下隐患,这样的软性限制肯定是不行的。程序是严谨的,我们需要使用程序去限制各个层之间的关系

前面我们说到了QFramework定义了IBelongToArchitecture和ICanSetArchitecture接口,实现静态拓展使得我们可以很方便的使用this关键字获取Architecture,同样QFramework使用静态拓展的方法实现了对各个层级在程序上的硬限制,我们以ICanGetModel接口为例

1
2
3
4
5
6
7
8
9
   public interface ICanGetModel : IBelongToArchitecture
{
}

public static class CanGetModelExtension
{
public static T GetModel<T>(this ICanGetModel self) where T : class, IModel =>
self.GetArchitecture().GetModel<T>();
}

我们可以看到,ICanGetModel接口继承了IBelongToArchitecture接口,该接口有一个GetArchitecture()方法使得该实现该接口的类可以获取Architecture,根据静态方法,我们就可以在实现ICanGetModel接口的类中,使用this关键字获取Model,而没有实现ICanGetModel的话,就不能使用,这样,就形成了限制。同理,其他层级也是类似的原理。这样我们在各个层级对应的接口中,就可以通过实现对应的规则接口(Rules),来打到限制,例如

1
2
3
4
5
public interface ISystem : IBelongToArchitecture, ICanSetArchitecture, ICanGetModel, ICanGetUtility,
ICanRegisterEvent, ICanSendEvent, ICanGetSystem
{
void Init();
}

这样,就实现了系统层
- 可以获取System
- 可以获取Model
- 可以监听Event
- 可以发送Event
从而形成了限制,达到了程序成面上真正的分层

接下来,QFramework使用BindableProperty,TypeEvent,EasyEvent实现MVC,用Command分担ViewControler的逻辑等其他附加的功能,最终构成了QFramework的核心架构,此外QFramework还提供了可以脱离核心架构的常用的工具集,例如UIKit,AudioKit,ActionKit等,使得在开发游戏时使用QFramework会更加简便

About this Post

This post is written by Yuan, licensed under CC BY-NC 4.0.

#Unity