502 lines
17 KiB
Markdown
502 lines
17 KiB
Markdown
# QFramework Core 细读与 FlowScope 借鉴边界
|
||
|
||
更新时间:2026-06-12
|
||
|
||
参考源码:<https://github.com/liangxiegame/QFramework/blob/master/QFramework.cs>
|
||
|
||
本文分析 QFramework 单文件 Core 的架构设计、适用范围、优缺点,以及它对 FlowScope Kernel / CoreModules 的可借鉴部分。本文不是选型结论,也不是实现计划;它用于后续讨论 FlowScope 业务架构层、事件层、状态层时作为参考。
|
||
|
||
## 1. 总结
|
||
|
||
QFramework Core 的价值不在于提供完整商业游戏运行时,而在于用很少的类型固定 Unity 业务代码的依赖方向。
|
||
|
||
它的核心模型可以概括为:
|
||
|
||
```text
|
||
Architecture<T>
|
||
-> IOCContainer
|
||
-> Model / System / Utility
|
||
-> Command / Query
|
||
-> Event / BindableProperty
|
||
|
||
Controller / MonoBehaviour
|
||
-> SendCommand / SendQuery
|
||
-> GetModel / GetSystem
|
||
-> RegisterEvent
|
||
```
|
||
|
||
QFramework Core 解决的是 Unity 项目里常见的“表现层乱改状态、MonoBehaviour 互相引用、业务逻辑没有入口、通知链路散落”的问题。它不是资源框架、UI 框架、热更框架、构建框架,也不负责 Feature、场景、流程或模块生命周期编排。
|
||
|
||
对 FlowScope 来说,QFramework Core 最值得借鉴的是:
|
||
|
||
- 状态变更必须有入口。
|
||
- 上层可以调用下层,下层不直接引用上层。
|
||
- 下层通过事件或可观察状态通知上层。
|
||
- Command / Query 是短生命周期行为对象,不承载长期状态。
|
||
- 用能力接口限制对象可访问的架构能力。
|
||
|
||
不适合直接照搬的是:
|
||
|
||
- 静态全局 `Architecture<T>.Interface` 入口。
|
||
- 强制 `Model / System / Utility / Command` 成为 Kernel 业务分层。
|
||
- 没有 RootScope / FeatureScope 的单容器模型。
|
||
- 没有 Feature 级生命周期和释放边界。
|
||
- 把 Event / BindableProperty 作为所有项目默认基础设施。
|
||
|
||
## 2. Core 范围
|
||
|
||
QFramework.cs 里的核心组成大致分为以下几组。
|
||
|
||
| 组 | 类型 | 职责 |
|
||
| --- | --- | --- |
|
||
| 架构根 | `IArchitecture`, `Architecture<T>` | 注册和获取 Model / System / Utility,执行 Command / Query,发送和注册事件 |
|
||
| 表现层协议 | `IController` | 通常由 MonoBehaviour 实现,负责接入架构,不直接持有业务状态 |
|
||
| 业务逻辑层 | `ISystem`, `AbstractSystem` | 放跨表现层共享的业务逻辑 |
|
||
| 数据层 | `IModel`, `AbstractModel` | 放状态和数据操作 |
|
||
| 工具层 | `IUtility` | 放外部能力适配,例如存储、SDK、序列化 |
|
||
| 行为对象 | `ICommand`, `ICommand<TResult>`, `IQuery<TResult>` | 封装写操作和读操作 |
|
||
| 能力接口 | `ICanGetModel`, `ICanSendCommand` 等 | 用接口组合限制对象能做什么 |
|
||
| 事件系统 | `TypeEventSystem`, `EasyEvent` | 按类型注册、发送、反注册事件 |
|
||
| 可绑定状态 | `BindableProperty<T>` | 值变化时通知订阅者 |
|
||
| 容器 | `IOCContainer` | 按类型注册和解析实例 |
|
||
|
||
这个范围说明 QFramework Core 更接近“业务代码架构层”,而不是“应用 Kernel”。它把代码写法约束住,但不管理完整应用运行期。
|
||
|
||
## 3. Architecture<T>
|
||
|
||
`Architecture<T>` 是 QFramework 的架构根。它使用静态字段保存当前架构实例,`Interface` 首次访问时触发初始化。
|
||
|
||
典型启动过程:
|
||
|
||
1. 创建 `T : Architecture<T>` 实例。
|
||
2. 调用子类实现的 `Init()`。
|
||
3. 在 `Init()` 中注册 Model、System、Utility。
|
||
4. 执行 `OnRegisterPatch`。
|
||
5. 初始化所有未初始化的 Model。
|
||
6. 初始化所有未初始化的 System。
|
||
7. 将架构标记为已初始化。
|
||
|
||
反初始化过程:
|
||
|
||
1. 调用 `OnDeinit()`。
|
||
2. 对已初始化的 System 调用 `Deinit()`。
|
||
3. 对已初始化的 Model 调用 `Deinit()`。
|
||
4. 清空容器。
|
||
5. 清空静态架构实例。
|
||
|
||
这个设计很适合中小项目:入口少、初始化简单、读源码很快。它的问题也同样明确:全局静态入口会自然演化成 Service Locator,业务对象很容易绕开组合根直接拿依赖。
|
||
|
||
FlowScope 不应该照搬这个入口。FlowScope 的 Kernel 需要显式 `AppKernel`、`RootScope`、`StartupPipeline` 和 `FeatureHost`,让启动顺序、依赖边界和关闭顺序都可测试、可替换、可释放。
|
||
|
||
## 4. Model / System / Utility
|
||
|
||
QFramework 的分层规则可以简化理解为:
|
||
|
||
```text
|
||
Controller -> Command / Query -> System / Model -> Utility
|
||
Model / System -> Event / BindableProperty -> Controller
|
||
```
|
||
|
||
### Model
|
||
|
||
`IModel` 表示数据层。它可以:
|
||
|
||
- 持有状态。
|
||
- 调用 Utility。
|
||
- 发送事件。
|
||
- 初始化和反初始化。
|
||
|
||
它不应该直接引用 Controller,也不应该直接操作 UI。
|
||
|
||
### System
|
||
|
||
`ISystem` 表示共享业务逻辑层。它可以:
|
||
|
||
- 获取 Model。
|
||
- 获取 Utility。
|
||
- 获取其他 System。
|
||
- 注册事件。
|
||
- 发送事件。
|
||
- 初始化和反初始化。
|
||
|
||
System 适合放跨 UI、跨场景、跨表现层复用的业务逻辑。它比 Model 更偏行为,比 Controller 更偏领域。
|
||
|
||
### Utility
|
||
|
||
`IUtility` 是最薄的一层,只是一个标记接口。它通常承载外部能力,例如:
|
||
|
||
- 本地存储。
|
||
- 网络 SDK。
|
||
- 平台 SDK。
|
||
- JSON 序列化。
|
||
- 时间服务。
|
||
|
||
Utility 本身不被强制注入 Architecture,这也意味着它更像纯工具或外部适配器。
|
||
|
||
### 对 FlowScope 的启发
|
||
|
||
FlowScope Kernel 不应该规定 `Model / System / Utility`。这是项目业务架构选择,不是 Kernel 机制。
|
||
|
||
但 FlowScope 可以在 CoreModules 或推荐模板里提供类似规则:
|
||
|
||
- Feature 内部状态归 Feature 自己或 Feature-scoped state service。
|
||
- 跨 Feature 长期状态归 Root-scoped service。
|
||
- 外部能力通过接口注册到 RootScope。
|
||
- UI 和 MonoBehaviour 不直接写长期状态,而是调用 UseCase、Command 或 Service 方法。
|
||
|
||
## 5. Command / Query
|
||
|
||
QFramework 用 Command 作为状态变更入口,用 Query 作为读操作入口。
|
||
|
||
Command 的特征:
|
||
|
||
- 短生命周期。
|
||
- 执行前被注入 Architecture。
|
||
- 可获取 Model / System / Utility。
|
||
- 可发送事件。
|
||
- 可继续发送其他 Command 或 Query。
|
||
- 不保存长期状态。
|
||
|
||
Query 的特征:
|
||
|
||
- 短生命周期。
|
||
- 执行前被注入 Architecture。
|
||
- 主要读取 Model / System。
|
||
- 返回查询结果。
|
||
|
||
这个设计的好处是把“写操作”从 Controller 中拿出来。Controller 不再到处写:
|
||
|
||
```csharp
|
||
model.Coins.Value += 10;
|
||
```
|
||
|
||
而是写:
|
||
|
||
```csharp
|
||
this.SendCommand(new AddCoinCommand(10));
|
||
```
|
||
|
||
这样状态变化可以被搜索、测试和复用。
|
||
|
||
对 FlowScope 来说,这个思想很值得吸收,但名称和形态不应进 Kernel。Kernel 不应该强制所有状态变更都叫 Command。项目可以选择:
|
||
|
||
- `Command`
|
||
- `UseCase`
|
||
- `Action`
|
||
- `Service Method`
|
||
- `Application Service`
|
||
|
||
Kernel 只需要保证依赖边界和生命周期,不需要规定业务行为对象的命名体系。
|
||
|
||
## 6. 能力接口组合
|
||
|
||
QFramework 一个很好的设计点是能力接口组合。
|
||
|
||
例如:
|
||
|
||
- `ICanGetModel`
|
||
- `ICanGetSystem`
|
||
- `ICanGetUtility`
|
||
- `ICanSendCommand`
|
||
- `ICanSendEvent`
|
||
- `ICanRegisterEvent`
|
||
- `ICanSendQuery`
|
||
|
||
这些接口本身几乎没有实现,实际能力通过扩展方法提供。对象只有实现了对应接口,才获得对应扩展方法。
|
||
|
||
这带来的好处是:
|
||
|
||
- Controller 能做的事和 Model 能做的事不同。
|
||
- Model 可以发事件,但不能随意发 Command。
|
||
- Command 可以访问 Model/System/Utility,但不变成长期服务。
|
||
- 架构规则体现在类型系统里,而不是只写在文档里。
|
||
|
||
这个方向对 FlowScope 有参考价值。FlowScope 如果后续做业务架构层,可以避免提供一个万能 `FeatureContext`。更好的方式是拆小能力:
|
||
|
||
```text
|
||
ICanResolveService
|
||
ICanPublishEvent
|
||
ICanReadState
|
||
ICanExecuteUseCase
|
||
ICanRegisterLifetime
|
||
```
|
||
|
||
但这些应属于 P1/P2 业务层或扩展层。Kernel 的 `FeatureContext` 仍应保持最小,只暴露 Scope、Lifetime、CancellationToken、Handle 等机制概念。
|
||
|
||
## 7. TypeEventSystem
|
||
|
||
QFramework 的 `TypeEventSystem` 是一个按事件类型分发的轻量事件系统。
|
||
|
||
它的核心机制:
|
||
|
||
```text
|
||
TypeEventSystem
|
||
-> EasyEvents
|
||
-> Dictionary<Type, IEasyEvent>
|
||
-> EasyEvent<T>
|
||
```
|
||
|
||
发送事件时按 `EasyEvent<T>` 类型取出事件对象并触发。注册事件时如果没有对应事件对象,就创建一个。
|
||
|
||
优点:
|
||
|
||
- 使用简单。
|
||
- 类型安全。
|
||
- 适合 UI 刷新和轻量业务通知。
|
||
- 反注册模型清晰。
|
||
|
||
风险:
|
||
|
||
- 没有作用域隔离。
|
||
- 没有事件优先级。
|
||
- 没有异步派发协议。
|
||
- 没有错误隔离。
|
||
- 没有事件链路追踪。
|
||
- 大型项目里容易变成全局黑箱。
|
||
|
||
FlowScope 可以借鉴“下层发事件,上层订阅”的方向,但 EventBus 不应进入 Kernel 主依赖。更合理的位置是 `FlowScope.Events` 或 P1 Events 模块:
|
||
|
||
```text
|
||
Kernel
|
||
不知道 EventBus
|
||
|
||
P1 Events
|
||
注册 IEventBus 到 RootScope
|
||
提供 FeatureLifetime 可登记的订阅句柄
|
||
提供同步或异步事件策略
|
||
```
|
||
|
||
这样 Feature 可以使用事件,但 Kernel 不被事件模型绑死。
|
||
|
||
## 8. BindableProperty<T>
|
||
|
||
`BindableProperty<T>` 是一个可观察值对象。它在 `Value` 变化时触发 `EasyEvent<T>`,并提供:
|
||
|
||
- `Register`
|
||
- `RegisterWithInitValue`
|
||
- `UnRegister`
|
||
- `SetValueWithoutEvent`
|
||
- 自定义比较器
|
||
|
||
它适合表达 UI 绑定和简单状态监听,例如金币、生命值、进度条、开关状态。
|
||
|
||
优点:
|
||
|
||
- 简单直接。
|
||
- UI 订阅体验好。
|
||
- `RegisterWithInitValue` 避免 UI 初始刷新遗漏。
|
||
- 对中小项目足够实用。
|
||
|
||
风险:
|
||
|
||
- 比较器是静态泛型级别配置,使用时要小心全局影响。
|
||
- 复杂状态容易被拆成大量可绑定字段,导致状态事务边界不清。
|
||
- 不适合表达复杂异步流、错误流、完成流。
|
||
- 如果到处暴露可写 `BindableProperty<T>`,仍然会出现状态乱改。
|
||
|
||
FlowScope 可以把类似能力放到状态或 UI 扩展模块,但不应放进 Kernel。Kernel 只负责 Feature 生命周期;状态响应式属于业务层或表现层。
|
||
|
||
## 9. IOCContainer
|
||
|
||
QFramework 的 `IOCContainer` 很小:
|
||
|
||
- `Register<T>(T instance)` 按 `typeof(T)` 注册。
|
||
- `Get<T>()` 按 `typeof(T)` 获取。
|
||
- `GetInstancesByType<T>()` 枚举所有兼容类型实例。
|
||
- `Clear()` 清空。
|
||
|
||
优点:
|
||
|
||
- 极易理解。
|
||
- 调试成本低。
|
||
- 足够支撑 QFramework 的 Model/System/Utility 注册。
|
||
|
||
不足:
|
||
|
||
- 没有构造函数注入。
|
||
- 没有 Scope。
|
||
- 没有生命周期释放。
|
||
- 没有 `IDisposable` / `IAsyncDisposable` 管理。
|
||
- 没有多绑定。
|
||
- 没有 named/keyed service。
|
||
- 没有循环依赖检测。
|
||
- 没有线程安全。
|
||
|
||
FlowScope 可以借鉴“按类型注册和解析”的最小体验,但实现必须比 QFramework 的容器多一层生命周期能力:
|
||
|
||
```text
|
||
RootScope
|
||
长生命周期服务
|
||
|
||
FeatureScope
|
||
Feature 实例和本次启动参数
|
||
先查本地,再回退 RootScope
|
||
|
||
FeatureLifetime
|
||
释放运行期句柄、订阅、资源组、临时对象
|
||
```
|
||
|
||
这也是 FlowScope 不能直接采用 QFramework IOC 的核心原因。
|
||
|
||
## 10. 生命周期对比
|
||
|
||
| 维度 | QFramework Core | FlowScope Kernel 目标 |
|
||
| --- | --- | --- |
|
||
| 应用入口 | 静态 `Architecture<T>.Interface` | 显式 `AppKernel` |
|
||
| 启动流程 | 首次访问时初始化 | `StartupPipeline` 顺序执行 |
|
||
| 依赖容器 | 单个 `IOCContainer` | `RootScope + FeatureScope` |
|
||
| 业务运行单元 | Model/System 长驻 | Feature 实例 |
|
||
| Feature 生命周期 | 不负责 | `LoadAsync / EnterAsync / ExitAsync` |
|
||
| 释放边界 | Model/System Deinit,容器 Clear | `FeatureLifetime + FeatureScope + RootScope` |
|
||
| 异步关闭 | 不突出 | Kernel 必须处理 async stop/shutdown |
|
||
| 运行位置 | 不负责 | `FeatureSlot + FeaturePolicy` |
|
||
|
||
QFramework 的生命周期适合“一个游戏一个 Architecture,若干长驻 Model/System”。FlowScope 的目标是“一个应用 Kernel 管多个 Feature 实例,每个 Feature 有独立依赖边界和释放边界”。
|
||
|
||
这两个方向不冲突,但层级不同。QFramework 更像可选的业务组织方式,FlowScope Kernel 更像运行机制。
|
||
|
||
## 11. 与完整框架的横向比较
|
||
|
||
| 维度 | QFramework Core | UnityGameFramework | ET Framework | TinaX | FlowScope 应吸收 |
|
||
| --- | --- | --- | --- | --- | --- |
|
||
| 核心定位 | 轻量业务分层 | 客户端模块全家桶 | 双端大型联网运行时 | 服务化 Package 框架 | 小 Kernel + 可选模块 |
|
||
| 启动入口 | Architecture | GameEntry / Procedure | Scene / Fiber / Actor | Core service bootstrap | AppKernel / StartupPipeline |
|
||
| 资源系统 | Core 不负责 | 内置 Resource | 可接入包和热更链路 | VFS | P1 Resources 模块 |
|
||
| UI | Core 不负责,UIKit 在工具生态 | 内置 UI 模块 | 通常通过包扩展 | UIKit | P1 UI 模块 |
|
||
| 事件 | TypeEventSystem | Event 模块 | 消息/事件体系 | Event/System service | P1 Events 模块 |
|
||
| 状态变更 | Command | 模块 API / Procedure | Entity/System/消息 | Service API | 可选 Command/UseCase |
|
||
| 工程流水线 | 不负责 | 部分覆盖 | 强工具链 | Package 化 | Tools/Package 后续模块 |
|
||
|
||
QFramework 在这张表里的优势是轻量和清晰。它不应该被拿来和 UGF 的资源/UI/Procedure 覆盖度硬比,也不应该被要求承担 BDFramework 的热更构建流水线职责。
|
||
|
||
## 12. 对 FlowScope 的建议
|
||
|
||
### 12.1 Kernel 不吸收 QFramework 分层
|
||
|
||
FlowScope Kernel 继续保持:
|
||
|
||
```text
|
||
AppKernel
|
||
StartupPipeline
|
||
FeatureHost
|
||
Container
|
||
FeatureLifetime
|
||
FeatureSlot
|
||
FeaturePolicy
|
||
IFeature
|
||
FeatureContext
|
||
FeatureHandle
|
||
```
|
||
|
||
Kernel 不加入:
|
||
|
||
- `IModel`
|
||
- `ISystem`
|
||
- `IUtility`
|
||
- `ICommand`
|
||
- `IQuery`
|
||
- `BindableProperty`
|
||
- `TypeEventSystem`
|
||
- `IController`
|
||
|
||
原因是这些属于业务架构风格,不是 Feature 编排机制。
|
||
|
||
### 12.2 CoreModules 可吸收状态变更规则
|
||
|
||
FlowScope 可以在后续 CoreModules 或 template 中提供推荐规则:
|
||
|
||
```text
|
||
View / MonoBehaviour
|
||
-> Command / UseCase / Service Method
|
||
-> State Service / Feature Local State
|
||
-> Event / Observable
|
||
-> View / Presenter / Controller
|
||
```
|
||
|
||
这能吸收 QFramework 的优点,同时避免 Kernel 被业务范式锁死。
|
||
|
||
### 12.3 Events 模块可参考 TypeEventSystem,但要补足边界
|
||
|
||
如果 FlowScope 做 P1 Events,不建议只复制 `TypeEventSystem`。至少要明确:
|
||
|
||
- 事件作用域:Root-scoped 还是 Feature-scoped。
|
||
- 订阅句柄如何进入 FeatureLifetime。
|
||
- 事件处理异常是否聚合、记录或继续派发。
|
||
- 是否支持异步事件。
|
||
- 是否允许跨 Feature 事件。
|
||
- 是否提供调试追踪。
|
||
|
||
QFramework 的 TypeEventSystem 可作为最小同步事件模型参考,但不是最终模块规格。
|
||
|
||
### 12.4 State 模块可参考 BindableProperty,但不要裸露可写状态
|
||
|
||
如果 FlowScope 提供类似 BindableProperty 的能力,建议区分:
|
||
|
||
```text
|
||
IReadonlyState<T>
|
||
IMutableState<T>
|
||
StateWriter<T>
|
||
StateChanged<T>
|
||
```
|
||
|
||
UI 层优先拿只读接口,写入通过 UseCase/Command/Service。这样比直接把 `BindableProperty<T>` 暴露给所有人更稳。
|
||
|
||
### 12.5 业务模板可以提供 QFramework 风格
|
||
|
||
可以考虑后续提供一个可选模板:
|
||
|
||
```text
|
||
FlowScope.BusinessArchitecture
|
||
- ICommand
|
||
- ICommand<TResult>
|
||
- IQuery<TResult>
|
||
- IUseCase
|
||
- IStateService
|
||
- IEventPublisher
|
||
```
|
||
|
||
但这应是项目选择,不是 Kernel 强制。
|
||
|
||
## 13. 风险清单
|
||
|
||
如果直接照搬 QFramework Core 到 FlowScope,主要风险是:
|
||
|
||
1. Kernel 变成业务架构框架,失去机制最小化。
|
||
2. `Architecture<T>.Interface` 和 `RootScope` 形成双全局入口。
|
||
3. `IOCContainer` 无法表达 FeatureScope 生命周期。
|
||
4. Command/Event 进入 Kernel 后,P1/P2 模块会被迫统一业务风格。
|
||
5. EventBus 变成隐式跨 Feature 通道,削弱 FeatureHost 的显式编排边界。
|
||
6. BindableProperty 进入底层后,UI/状态响应式会反向污染 Kernel。
|
||
|
||
规避方式:
|
||
|
||
- Kernel 只保留生命周期、依赖边界、Feature 编排。
|
||
- QFramework 的分层规则只进入文档、模板或可选模块。
|
||
- Event/State/Command 都作为 CoreModules 或扩展层设计。
|
||
- FeatureContext 不提供全能业务能力。
|
||
|
||
## 14. 结论
|
||
|
||
QFramework Core 是一个优秀的轻量 Unity 业务架构样本。它用很少代码表达了三条关键规则:
|
||
|
||
- 上层驱动下层。
|
||
- 状态变更有入口。
|
||
- 下层通过通知影响上层。
|
||
|
||
FlowScope 应吸收这些规则,但不能把 QFramework 的 Core 形态直接变成自己的 Kernel。FlowScope Kernel 的目标更底层:它负责应用启动、依赖 Scope、Feature 生命周期、运行槽和释放链。QFramework 风格更适合作为 FlowScope 的可选业务层、示例模板或 P1/P2 模块设计参考。
|
||
|
||
最终边界建议:
|
||
|
||
```text
|
||
FlowScope.Kernel
|
||
只负责机制
|
||
|
||
FlowScope.CoreModules
|
||
提供 Events / State / Config / Save / Resources / UI 等模块
|
||
|
||
FlowScope.BusinessTemplate.QFrameworkLike
|
||
可选提供 Command / Query / State / Event 的业务写法参考
|
||
```
|
||
|
||
这样既能保留 QFramework 的轻量分层优点,又不会牺牲 FlowScope 当前“Kernel tiny, modules optional”的主方向。
|