vampire-like/docs/UI-5层架构设计规范.md

10 KiB
Raw Permalink Blame History

UI 五层架构设计规范UseCase / RawData / Controller / Context / View

1. 文档目标

本文定义一套可长期复用的 UI 分层设计方案,用于约束 UI 模块的职责边界、依赖方向、通信方式和测试策略。

  • 本文描述的是规范,不是对某个项目现状的总结
  • 项目内目录或基类只作为落地示例,不改变本文的抽象约束
  • 本文重点约束 UIForm 级模块;子组件(ItemArea 等)可只实现 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 是纯展示数据,不进入 UseCase
  • UseCase 不能返回 Context
  • View 只能消费 Context

2.3 Controller 是 UI 唯一外部入口

对于 UIForm 级模块,外部流程只能通过 Controller 驱动 UI

  • 打开 UI
  • 关闭 UI
  • 绑定 UseCase
  • 刷新 UI
  • 响应 UI 专用事件

View 不作为外部流程的直接依赖对象。

3. 五层职责定义

3.1 UseCase 层

职责:封装 UI 对应的业务用例,负责业务规则、状态推进、校验和纯业务结果输出。

约束:

  • 实现 IUIUseCase
  • 命名:XXXFormUseCase
  • 对外提供语义化方法,例如 CreateInitialModelTryRefreshSelectConfirm
  • 返回值只能是 RawData 或纯业务结果对象,例如 XXXResultXXXActionResult
  • 不依赖 ContextViewUGuiFormMonoBehaviour 等 UI 类型
  • 不负责 UI 资源加载、文本拼装、颜色选择、图标转换等展示处理
  • 不发布 UI 专用事件

适用场景:

  • UI 会读写领域状态
  • UI 存在明确业务规则、条件分支、校验或状态推进
  • UI 的交互结果需要被测试和复用

3.2 RawData 层

职责:承载 UseCase -> Controller 的纯业务传输模型。

约束:

  • 命名:XXXFormRawData
  • 只描述业务数据,不包含 UI 展示行为
  • 可以包含领域对象、配置对象、标识符、枚举、数值和纯数据集合
  • 不允许依赖 ContextViewSpriteTMP_Text 等展示相关类型
  • 不允许直接使用 XXXItemContextXXXFormContext 作为字段类型

说明:

  • RawData 的目标是表达“业务上发生了什么”
  • Context 的目标是表达“界面应该怎么显示”
  • 两者不能混用

3.3 Controller 层

职责UI 编排层,负责连接外部流程、UseCaseView,并统一管理 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
  • 命名:XXXFormContextXXXItemContextXXXAreaContext
  • 只能由 Controller 构建和更新
  • 字段以展示友好为目标,例如标题、描述、图标、颜色、状态、列表、按钮文案
  • 允许组合子 Context
  • 不进入 UseCase

说明:

  • Context 可以包含展示层需要的最终数据
  • Context 可以是“已格式化”的显示数据
  • 但这些数据必须由 Controller 负责准备,而不是 UseCase

3.5 View 层

职责:纯表现层,负责控件绑定、渲染刷新、动画触发和交互事件抛出。

约束:

  • Form 类继承 UGuiForm,子组件通常继承 MonoBehaviour
  • 命名:XXXFormXXXItemXXXArea
  • 提供 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 / Result
  • Controller -> UseCase + RawData + Result + Context + View + UI 专用事件
  • Context -> 子 Context / 纯展示值对象
  • View -> Context + UI 专用事件

禁止依赖:

  • UseCase -> Context / View / Unity 具体展示组件
  • RawData / Result -> Context / View
  • Context -> View / UseCase
  • View -> UseCase
  • View -> 全局业务事件
  • View -> 领域状态修改

6. 事件通信规范

6.1 UI 与 Controller 的通信方式

ViewController 的通信通过当前 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 关闭流程

  1. 外部流程或 Controller 调用关闭逻辑
  2. Controller 解除事件订阅
  3. View.OnClose 清理本地视觉状态
  4. 下次打开时重新按 Context 初始化

8. 目录与命名规范

目录示例以 Assets/GameMain/Scripts/UI 为例。

  • 目录:UI/<SceneDomain>/UseCase|RawData|Controller|Context|View
  • 五层同名前缀保持一致,例如 ShopForm*LevelUpForm*
  • 子组件上下文命名:RoleItemContextRewardItemContextDisplayAreaContext
  • 结果对象命名:XXXResultXXXActionResult
  • 轻量 UI 可以省略 UseCaseRawData 目录,但不省略 ControllerContextView

9. 测试规范

9.1 自动化测试范围

如果一个 UI 具备 UseCase,并且需要补自动化测试,则统一使用 EditMode 测试。

优先覆盖:

  • 初始化模型生成
  • 业务分支与校验
  • 用户动作对应的结果对象
  • 边界条件和非法输入

9.2 Controller / View 的验证策略

ControllerView 以人工验收为主,重点验证:

  • 首次打开
  • 交互刷新
  • 局部刷新
  • 关闭重开
  • 非法参数或空数据输入时的表现

10. 落地检查清单

新增 UI 时,至少检查以下事项:

  1. 先判断该 UI 属于标准五层还是轻量 UI
  2. 如果存在业务规则或状态推进,必须引入 UseCase
  3. RawData 中不得出现 Context 类型
  4. Context 只能在 Controller 中构建
  5. View 不得订阅全局业务事件
  6. View 的交互只能通过 UI 专用事件上报
  7. Controller 必须成对管理事件订阅与解除订阅
  8. UseCase 且需要补自动化测试时,测试写入 EditMode

11. 非目标说明

  • 本文不讨论历史实现,也不为历史写法背书
  • 本文不要求所有 UI 必须强制引入 UseCase
  • 本文不展开底层 UI 框架或事件系统实现细节