10 KiB
10 KiB
UI 五层架构设计规范(UseCase / RawData / Controller / Context / View)
1. 文档目标
本文定义一套可长期复用的 UI 分层设计方案,用于约束 UI 模块的职责边界、依赖方向、通信方式和测试策略。
- 本文描述的是规范,不是对某个项目现状的总结
- 项目内目录或基类只作为落地示例,不改变本文的抽象约束
- 本文重点约束 UIForm 级模块;子组件(
Item、Area等)可只实现Context + View - Unity GameFramework 的底层细节不在本文展开,本文只约束项目内 UI 代码组织
2. 核心原则
2.1 单向数据职责
UI 的职责链路固定为:
外部流程
-> Controller
-> UseCase
-> RawData / Result
-> BuildContext
-> View
View
--(UI 专用事件)--> Controller
说明:
UseCase只负责业务规则、状态推进和纯业务数据生成Controller是 UI 编排中心,也是唯一允许构建Context的层View只负责渲染和抛出交互,不直接处理业务状态
2.2 严格分离业务数据与展示数据
RawData是纯业务数据,不承载展示模型Context是纯展示数据,不进入UseCaseUseCase不能返回ContextView只能消费Context
2.3 Controller 是 UI 唯一外部入口
对于 UIForm 级模块,外部流程只能通过 Controller 驱动 UI:
- 打开 UI
- 关闭 UI
- 绑定
UseCase - 刷新 UI
- 响应 UI 专用事件
View 不作为外部流程的直接依赖对象。
3. 五层职责定义
3.1 UseCase 层
职责:封装 UI 对应的业务用例,负责业务规则、状态推进、校验和纯业务结果输出。
约束:
- 实现
IUIUseCase - 命名:
XXXFormUseCase - 对外提供语义化方法,例如
CreateInitialModel、TryRefresh、Select、Confirm - 返回值只能是
RawData或纯业务结果对象,例如XXXResult、XXXActionResult - 不依赖
Context、View、UGuiForm、MonoBehaviour等 UI 类型 - 不负责 UI 资源加载、文本拼装、颜色选择、图标转换等展示处理
- 不发布 UI 专用事件
适用场景:
- UI 会读写领域状态
- UI 存在明确业务规则、条件分支、校验或状态推进
- UI 的交互结果需要被测试和复用
3.2 RawData 层
职责:承载 UseCase -> Controller 的纯业务传输模型。
约束:
- 命名:
XXXFormRawData - 只描述业务数据,不包含 UI 展示行为
- 可以包含领域对象、配置对象、标识符、枚举、数值和纯数据集合
- 不允许依赖
Context、View、Sprite、TMP_Text等展示相关类型 - 不允许直接使用
XXXItemContext、XXXFormContext作为字段类型
说明:
RawData的目标是表达“业务上发生了什么”Context的目标是表达“界面应该怎么显示”- 两者不能混用
3.3 Controller 层
职责:UI 编排层,负责连接外部流程、UseCase、View,并统一管理 UI 生命周期与展示状态。
约束:
- UIForm 级模块默认必须有
Controller - 命名:
XXXFormController - 可基于
UIFormControllerCommonBase<TContext, TForm>实现 - 通过
BindUseCase(IUIUseCase)注入用例并做类型校验 OpenUI(object userData = null)负责接受外部参数、准备数据并打开 UI- 负责
RawData / Result -> Context的转换,常见形式为BuildContext - 负责事件订阅与解除订阅,且必须成对出现
- 负责全量刷新与局部刷新策略
- 负责过滤 UI 专用事件的
sender,确保事件只作用于当前 UI 实例
允许职责:
- 将业务数据转换为展示友好的文本、图标、颜色、列表状态
- 在必要时查询本地化、资源映射或展示适配逻辑
禁止职责:
- 在
Controller中堆叠大段领域业务规则 - 绕过
Context直接把业务对象塞给View - 直接修改其他 UI 的内部
View
3.4 Context 层
职责:承载“可直接驱动 UI 展示”的上下文数据。
约束:
- 继承
UIContext - 命名:
XXXFormContext、XXXItemContext、XXXAreaContext - 只能由
Controller构建和更新 - 字段以展示友好为目标,例如标题、描述、图标、颜色、状态、列表、按钮文案
- 允许组合子
Context - 不进入
UseCase
说明:
Context可以包含展示层需要的最终数据Context可以是“已格式化”的显示数据- 但这些数据必须由
Controller负责准备,而不是UseCase
3.5 View 层
职责:纯表现层,负责控件绑定、渲染刷新、动画触发和交互事件抛出。
约束:
- Form 类继承
UGuiForm,子组件通常继承MonoBehaviour - 命名:
XXXForm、XXXItem、XXXArea - 提供
RefreshUI(Context)、OnInit(Context)、OnReset()等渲染入口 - 只消费
Context - 用户交互通过 UI 专用事件通知
Controller - 不承载业务规则、流程推进、数据筛选和领域状态修改
- 不订阅全局业务事件
允许职责:
- 本地控件显隐
- 动画播放
- 一次性的纯视觉状态缓存
禁止职责:
- 直接调用
UseCase - 直接修改领域状态
- 订阅或处理全局业务事件
- 将自己作为业务逻辑的入口
4. UI 类型分级
4.1 标准五层 UI
组成:UseCase + RawData + Controller + Context + View
适用条件:
- UI 需要读写领域状态
- UI 存在明确业务规则或分支
- UI 交互会改变游戏流程、角色状态、背包、战斗结果等业务对象
- UI 行为需要被自动化测试覆盖
默认规则:
- 新增业务型 UI,优先使用完整五层
4.2 轻量 UI
组成:Controller + Context + View
说明:
UseCase对轻量 UI 不强制RawData也不是强制层,可按需要补充- 轻量 UI 仍然必须通过
Controller驱动,不能让View直接承担外部入口
适用条件:
- 只承担展示、导航、确认、提示等轻量职责
- 没有独立的业务规则或状态推进
- 只需要把已有参数转换成界面展示
升级规则:
- 一旦轻量 UI 开始承载业务规则、校验或状态推进,应升级为标准五层 UI
5. 依赖方向约束
允许依赖:
UseCase -> 领域对象 / 纯业务服务 / RawData / ResultController -> UseCase + RawData + Result + Context + View + UI 专用事件Context -> 子 Context / 纯展示值对象View -> Context + UI 专用事件
禁止依赖:
UseCase -> Context / View / Unity 具体展示组件RawData / Result -> Context / ViewContext -> View / UseCaseView -> UseCaseView -> 全局业务事件View -> 领域状态修改
6. 事件通信规范
6.1 UI 与 Controller 的通信方式
View 与 Controller 的通信通过当前 UI 模块专用事件完成。
约束:
- UI 专用事件只服务于当前 UI 模块
- 这些事件不是业务公共事件
- 业务模块、流程模块、领域模块不应复用这些事件
- 如果底层使用全局事件总线实现,也只能把它当作“传输通道”,不能把事件语义扩散成全局契约
6.2 事件边界
View -> Controller:使用 UI 专用事件Controller -> View:通过刷新Context或调用View的渲染接口Controller -> UseCase:直接方法调用UseCase -> Controller:通过返回RawData / Result,不通过 UI 事件反推界面
6.3 事件安全要求
Controller必须校验事件sender- 同类 UI 可同时存在时,事件必须只作用于当前窗体实例
- UI 专用事件命名应体现模块归属,避免语义过宽
7. 标准交互流程
7.1 有 UseCase 的标准流程
Procedure / GameState
-> 创建 UseCase
-> BindUseCase
-> OpenUI
Controller
-> UseCase.CreateInitialModel()
-> BuildContext(rawData)
-> View.RefreshUI(context)
View
--(UI 专用事件)--> Controller
Controller
-> UseCase.Action(...)
-> Result / RawData
-> BuildContext / PartialRefresh
-> View.RefreshUI(...)
7.2 无 UseCase 的轻量流程
外部流程
-> OpenUI(userData)
Controller
-> BuildContext(userData)
-> View.RefreshUI(context)
View
--(UI 专用事件)--> Controller
Controller
-> 处理轻量逻辑或路由动作
-> 更新 Context / 打开其他 UI / 关闭当前 UI
7.3 关闭流程
- 外部流程或
Controller调用关闭逻辑 Controller解除事件订阅View.OnClose清理本地视觉状态- 下次打开时重新按
Context初始化
8. 目录与命名规范
目录示例以 Assets/GameMain/Scripts/UI 为例。
- 目录:
UI/<SceneDomain>/UseCase|RawData|Controller|Context|View - 五层同名前缀保持一致,例如
ShopForm*、LevelUpForm* - 子组件上下文命名:
RoleItemContext、RewardItemContext、DisplayAreaContext - 结果对象命名:
XXXResult、XXXActionResult - 轻量 UI 可以省略
UseCase和RawData目录,但不省略Controller、Context、View
9. 测试规范
9.1 自动化测试范围
如果一个 UI 具备 UseCase,并且需要补自动化测试,则统一使用 EditMode 测试。
优先覆盖:
- 初始化模型生成
- 业务分支与校验
- 用户动作对应的结果对象
- 边界条件和非法输入
9.2 Controller / View 的验证策略
Controller 和 View 以人工验收为主,重点验证:
- 首次打开
- 交互刷新
- 局部刷新
- 关闭重开
- 非法参数或空数据输入时的表现
10. 落地检查清单
新增 UI 时,至少检查以下事项:
- 先判断该 UI 属于标准五层还是轻量 UI
- 如果存在业务规则或状态推进,必须引入
UseCase RawData中不得出现Context类型Context只能在Controller中构建View不得订阅全局业务事件View的交互只能通过 UI 专用事件上报Controller必须成对管理事件订阅与解除订阅- 有
UseCase且需要补自动化测试时,测试写入 EditMode
11. 非目标说明
- 本文不讨论历史实现,也不为历史写法背书
- 本文不要求所有 UI 必须强制引入
UseCase - 本文不展开底层 UI 框架或事件系统实现细节