29.UnityMVC总结

29.总结


29.1 核心要点速览

概念与演示前提

  • Model 持数据与规则,View 把模型画到控件上,Controller 处理输入并决定何时调 Model、何时开/关哪块界面。
内容
案例操作 M 主界面、N 关闭;点主角进角色;升级写存档并刷新界面
工程顺序 资源进工程 → 场景里摆 Canvas 和预制 → 分辨率和层级确认无误 → 再写脚本

非 MVC 胖面板

MainPanelRolePanel 一类写法:static 自引用、Resources.Load,挂到 GameObject.Find("Canvas")。首次 ShowMeInstantiateSetActive(true)UpdateInfoHideMe 多用 SetActive(false)。若改成 Destroy,等于换了一套生命周期,和「隐藏复用」不是一回事。

数据散落在各面板里 PlayerPrefs 读写,UpdateInfo 把值填进 Text。升级后除了当前面板,往往还要 MainPanel.Panel.UpdateInfo() 这种跨面板静态调用——改需求时引用网会一起动,后面拆 MVC 主要针对这个。

手写 MVC 与数据通知

手写版把「改数」收到 PlayerModel,界面只订阅、只刷新:

  • PlayerModel:字段私有、对外只读;PlayerModel.Data 懒单例,Init 读档,LevUpSaveData
  • MainView / RoleView:Inspector 拖引用,UpdateInfo(PlayerModel data) 只负责把字刷上去,不写升级公式、不写存档。
  • MainController / RoleControllerShowMe / HideMe、按钮回调、向 Model 订阅;升级按钮只调 PlayerModel.Data.LevUp(),不直接改别的面板的控件。
职责
PlayerModel 数据与持久化唯一入口
MainView / RoleView 纯显示
MainController / RoleController 流程与输入

预制体:MVC 演示走 BaseFramework/UI/UGUI/MVC/;前面「常规」示例在 UGUI/Normal/,路径不要交叉。

Controller.Start 里常见顺序:GetComponent 取 View → 先 UpdateInfo(PlayerModel.Data) → 绑按钮(开角色界面调 RoleController.ShowMe() 等)→ PlayerModel.Data.AddEventListener(UpdateInfo),在私有 UpdateInfo 里转发给两个 View。MVCMain 继续用 M / N 切主界面。

// OnDestroy 里成对 Remove,避免 Controller 没了委托还指着它
void OnDestroy()
{
    PlayerModel.Data.RemoveEventListener(UpdateInfo);
}

胖面板与 MVC 演示对照

对比 胖面板 MVC 演示
改数据 Panel 内直接 PlayerPrefs 只通过 PlayerModel 的方法
跨界面刷新 MainPanel.Panel.UpdateInfo() 一类静态互调 Controller 听 Model,只更新自己的 View
预制目录 UGUI/Normal/ UGUI/MVC/
脚本 UI、存档、跳转搅在一起 View / Controller / Model 各管一截

多几个类不是目的;目的是数据入口单一、刷新走事件,面板之间少硬编码引用。项目小可以不服,项目一大这套约束会省很多扯皮。

MVX 变体(同一业务换管道)

MVP、课内 MVVM、MVE 共用同一套界面需求,差别在View 要不要直接认识模型类型,以及通知走 C# 委托还是全局事件管理器

MVPMVP_PlayerModel 与手写 PlayerModel 同构。View 尽量只剩控件;Presenter 的 UpdateInfo 里给 txtName 等赋值,并订阅 MVP_PlayerModel.DataOnDestroy 退订。预制在 UGUI/MVP/,入口 MVPMain

MVVM(课内简化):没有 WPF 那种双向绑定;BaseUGUIPanel + GetUIElement,逻辑仍写在 Panel 里。InitUpdateInfo + AddEventListenerOnButtonInitClick 按按钮名开子界面。入口 MVVMMainShowPanel / HidePanel

MVE:在上一套骨架上,Model 改完走 BaseEventManagerAddEventListener / EventTrigger,字符串 key 与 MVE_PlayerModel 泛型载荷配套。路径 UI_SAVE_PATH + "MVE/",入口 MVEMain

写法 View 与 Model 谁改控件 数据到界面
MVC View 有 UpdateInfo(Model) View Model 内 UnityAction
MVP View 弱依赖模型类型 Presenter 仍是 Model 的 UnityAction
MVVM 演示 BaseUGUIPanel 合一 Panel 同上
MVE 同 MVVM 演示 同左 BaseEventManager 广播

PureMVC:单核、接入与显隐刷新

教学工程按单核:一个 Facade。真要多个上下文用官方多核,别在单核里 new 出第二套 GameFacade

接入方式二选一:编译 DLL 丢 Plugins;或把源码里核心 / 接口 / 设计模式三块拷进工程,外面套 PureMVC 文件夹方便改。

通知名PureNotificationconst string,与 RegisterCommandListNotificationInterestsSendNotification 共用字面量。字符串 key 拼错一位常常编译通过、运行里「没反应」,集中定义能少踩这类坑。

数据PlayerDataObj 存字段;PlayerProxy 包一层,RetrieveProxy 取,Data 上挂实例。读档、升级、SaveData 在 Proxy;界面不自动跟 Proxy 变,一般由 Command 再 SendNotification(UPDATE_PLAYER_INFO, …) 把各 Mediator 拉起来。

MediatorListNotificationInterests 列要听的名字;HandleNotification 里看 NameBodyViewComponentSetView 前、或 HidePanelCommand 之后可能为空,先判再 as 成具体 View。

CommandRegisterCommand(通知, () => new 某Command())ExecuteBody,中间可以继续 SendNotification 串步骤。

步骤 行为
显示 Body 面板名注册或取 MediatorViewComponent == nullResources.Load(示例 BaseFramework/UI/UGUI/PureMVC/...),挂 UGUICanvasSetView
隐藏 DestroyViewComponent = null;与只 SetActive(false) 不是同一策略
刷新 面板就绪或升级后发 UPDATE_PLAYER_INFOBodyRetrieveProxy(PlayerProxy.NAME).Data,所有 interests 命中的 Mediator 一起更新
角色 干什么 系列里
Facade 对外发通知、取 Proxy/Mediator GameFacade.Instance
Proxy Data、存档与业务 PlayerProxy + PlayerDataObj
Mediator 绑 View、接通知、调 UpdateInfo NewMainViewMediator
Command 一步业务(显隐、升级等) ShowPanelCommandLevUpCommand

29.2 面试题精选

基础题

1. 为何用 PlayerProxy 包一层 PlayerDataObj

题目

玩家数据已经能用 POCO 表示,为什么还要注册 PlayerProxy,而不是写一个全局单例 Model 了事?

深入解析
  • PureMVC 里 Model 侧的标准入口是 Proxy:通过 Facade.RetrieveProxy(NAME) 取,数据放在 Data,和谁一起注册、何时反注册跟框架表走。
  • POCO 只承载字段;读档、LevUpSaveData 放在 Proxy 上,比把所有静态方法堆进一个 God 类好拆、好测。
  • 和本系列手写 PlayerModel 比:PureMVC 更强调「经 Facade 拿模型」和「用通知驱动界面」,而不是业务里到处 PlayerModel.Data
对比 手写 MVC PureMVC
取模型 静态 PlayerModel.Data RetrieveProxy,走注册表
结构 数据与逻辑常混在一个类 POCO + Proxy 分工
刷界面 UnityAction → Controller Command 发通知 → Mediator
答题示例

Proxy 是框架认的 Model 角色,Data 上挂真正的数据对象;读档改数在 Proxy,不在散落的静态里。

界面更新靠 Command 发通知、Mediator 接,而不是全局单例到处被直接调用。

参考文章
  • 22.PureMVC框架-Model数据层和Proxy代理模式

2. 为什么要单独建 PureNotification 常量类

题目

通知名就是字符串,直接 SendNotification("xxx") 有什么问题?

深入解析
  • 拼错一个字符,C# 编译器不管,运行期常见现象是「按钮没反应、断点进不去 Command」,排查成本高。
  • RegisterCommand、Mediator 的 interests、业务里的 SendNotification 必须引用同一串字面量;收成 const 类等于项目内通知字表,改名、全局搜索都简单。
答题示例

魔法字符串容易写错且错得安静。

集中定义成常量,注册 Command 和写 interests 时共用,改一处全项目对齐。

参考文章
  • 21.PureMVC框架-框架导入和通知名类

进阶题

1. RegisterCommand 为何用工厂每次 new Command

题目

注册时为什么写 () => new StartUpCommand(),而不是注册一个长期复用的 Command 实例?

深入解析
  • Command 里常会带本次执行的临时状态;复用同一个实例,上一次 Execute 留下的字段可能污染下一次。
  • 工厂配合 InitializeController 里的映射表:业务只发通知,「谁处理」在注册阶段配好,加流程多半是加一条映射而不是改调用栈。
做法 结果
每次 new 无跨次执行的脏状态
单例 Command 成员可能被上次执行污染
映射表注册 扩展加表项,调用方保持薄
答题示例

每次执行 new 一个 Command,避免实例上残留状态。

通知到命令类型的关系写在注册里,调用栈只负责发通知。

参考文章
  • 25.PureMVC框架-Facade外观模式和Command命令模式

2. 从发 SHOW_PANEL 到主界面刷上数据,中间经过谁

题目

按本系列示例:SendNotification(SHOW_PANEL, "MainPanel") 之后,数据是怎么进到 NewMainView 的?哪里断了会白屏或没数据?

深入解析
  • Facade 把通知派给已注册的 ShowPanelCommand
  • Command 按 Body 注册或取得 Mediator,需要时 Resources.Load 预制、SetParentSetView;随后再发 UPDATE_PLAYER_INFOBody 里塞 RetrieveProxy(PlayerProxy.NAME).Data
  • 主界面 Mediator 在 ListNotificationInterests 里声明了 UPDATE_PLAYER_INFO,在 HandleNotification 里把 Body 转成 PlayerDataObj,调用 NewMainView.UpdateInfo
  • 若启动流程里没 RegisterProxy,后面 RetrieveProxy 为空,界面能出来但没有玩家数据。

HidePanelCommandDestroy 后必须把 ViewComponentnull,否则下次 Show 可能误判「已有 View」而跳过实例化;这与只 SetActive(false) 的缓存策略不要混用。

Facade → ShowPanelCommand → Mediator + Load/SetView
  → SendNotification(UPDATE_PLAYER_INFO, Proxy.Data)
  → Mediator.HandleNotification → NewMainView.UpdateInfo
答题示例

Facade 转到 ShowPanelCommand,Command 负责 Mediator 和预制体,SetView 后再发更新玩家信息,Body 里是 Proxy 的数据。

Mediator 收到后在 HandleNotification 里把数据交给 View;Proxy 没注册好的话这一步拿不到数据。

参考文章
  • 26.PureMVC框架-显隐面板命令
  • 27.PureMVC框架-更新通知和升级命令

深度题

1. 单核下封装 GameFacade.Instance 的边界

题目

在 PureMVC 单核用法里,自己包一层 GameFacade.Instance 时要注意什么?多 new 一个会怎样?

深入解析
  • 约定整个进程一个 Facade;子类 Instancenew GameFacade() 赋给基类静态 instance,返回时 as GameFacade 才能调用子类扩展。
  • 若误造第二套 Facade,或把单核 API 和多核混着用,会出现通知进 A 表、Proxy 挂在 B 表,表现像「随机不响应」。
  • 多模块隔离应走官方多核,而不是单核里硬拆多套。
答题示例

单核就是全局一个 Facade,Instance 负责造那一个并挂到基类静态上。

多实例等于多套注册表,Command 和 Proxy 对不上号,问题难查。

参考文章
  • 21.PureMVC框架-框架导入和通知名类
  • 25.PureMVC框架-Facade外观模式和Command命令模式

2. 跨面板刷新:胖面板、手写 MVC、PureMVC 怎么各干各的

题目

胖面板里升级后要 MainPanel.Panel.UpdateInfo();手写 MVC 用模型事件;PureMVC 里数据变了界面怎么一起更新?LEV_UP 之后为什么还要发 UPDATE_PLAYER_INFO

深入解析
  • 胖面板:面板之间静态引用互调,耦合随功能线性涨,改一个界面容易牵一片。
  • 手写 MVC:PlayerModelUnityAction 通知各 Controller,各更新自己的 View;Add / Remove 必须成对,否则泄漏。
  • PureMVC:Proxy 改完数据后,由 Command SendNotification;多个 Mediator 可以听同一条 UPDATE_PLAYER_INFOBody 共享一份 PlayerDataObj,主界面和子面板等级、数值一致。LevUpCommand 里只改 Proxy 不会自动重绘,再发 UPDATE_PLAYER_INFO 才能把「数据已变」广播给所有关心的界面;省掉这条就要各 Mediator 自己去 RetrieveProxy,风格上又回到紧耦合。
模式 跨面板同步手段
胖面板 静态互调
手写 MVC Model 事件 → 多个 Controller
PureMVC 同一通知 + 共享 Body,一对多 Mediator
答题示例

胖面板是面板互相捅;MVC 是模型事件推给各个 Controller。

PureMVC 用通知广播,多个 Mediator 听同一条更新;升级后多发一次 UPDATE_PLAYER_INFO 是为了让所有订阅方拿到新 Data,否则只改 Proxy 界面不会自己变。

参考文章
  • 4.MVC框架-不使用MVC框架-主面板
  • 6.MVC框架-使用MVC框架-Model数据层
  • 27.PureMVC框架-更新通知和升级命令


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏