加载中...

本篇要回答的问题:把上一讲只能"增"不能"改删"的 MVP 版画图程序,做一次真实的需求迭代(增删改、填充色、新交互范式),借此观察 MVC 各角色对需求变更的敏感度

上一讲(实战一)我们把画图程序的 Model/ViewModel/Controller 三层走了一遍,但反馈显示有不少没交代清楚。所以本讲先把上一版补讲清楚(用三张耦合关系表把 M-V-C 串起来),再对程序做一次需求迭代——服务端对接顺延到下一讲。仍是单机版。

MVP 版画图程序

上一版本质是 MVP:只能增加新图形,不能删除、不能修改。用三张表把各层耦合关系讲透:

DOM 树只有三级:Document -> Shape -> LineStyle。Model 为 View / Controllers 提供了什么:

Model 与 View、Controllers 的耦合关系表

关键洞见:View 层对 Model 除了 onpaint 没有任何其他需求;各 Controller 看似要 Model 很多方法,实质目的只有一个——创建图形(addShape)

View 层只有 QPaintView 一个类,按职责归三类(Model 相关 / View 自身 / 为 Controller 服务):

QPaintView 三类职责拆分表

Controller 位于最上层,没人调它的方法,所以关注点放在每个 Controller 怎么用 Model 和 View(事件 Event 从 View 中单列):

各 Controller 对 Model/View/Event 的使用表

三表对照,M-V-C 如何关联就一目了然。

改进版的画图程序

MVP 版图形创建完就改不了,不好用。新版功能改进:

改进项
选择图形后可删除、移动、改样式
图形样式增加 fillColor(填充色)
更现代的交互范式:默认 ShapeSelector 状态,创建完图形自动回到此状态
选择图形后,界面当前样式自动更新为被选图形的样式

Model 层变化

改进版 Model 层规格表

Model 层 ChangeNotes 表

重点是一次不兼容修改(小重构)QLineStyle 改名 QShapeStyle,属性 width/color 改名 lineWidth/lineColor。

关键判断:重构关键是及时处理、把控质量。弱类型语言(JS)重构心智负担大,要辅以足够多的单元测试;这也是作者更喜欢静态类型语言的原因——漏改编译器会告诉你(但单测仍不可省)。

最初把构造函数最后一个参数设为 QLineStyle 是设计失误——否则每次加样式都要加参数。改为 QShapeStyle 后设计就完备了,未来样式演进集中到这一个类。对样式做更远的推演:

当前 QShapeStyle 未来重构后
lineWidth / lineColor / fillColor 平铺 QShapeStyle { line: any; fill: any },拆出 QLineStyle / QFillStyle

关键洞见:未来 line/fill 用 any 而非具体类型,是因为它们只是简单版样式(QFillStyle 更好的名字是 QSimpleFillStyle)——真实 GDI 的 FillStyle 还可以是图片平铺、渐变等,简单类表达不了。对需求演进的推演,关键是眼光看多远。

View 层变化

改进版 View 层规格表

View 层 ChangeNotes 表

除随 Model 小重构带来的 properties 改名 style 外,View 真正的变化是两个:

变化 说明
引入 selection 当前单选一个 shape,变化时发 onSelectionChanged 事件
引入 onControllerReset 事件 Controller 完成/放弃图形创建时发出,由 Menu 接收

onControllerReset 引出一个事件机制设计问题:要不要支持任意事件?单播还是多播?最通用是支持任意事件+多播(如 QEventManager:fire / addListener / removeListener)。但——

关键判断:View 事件机制要在通用性与可控性间平衡。一旦 View 聚合通用 QEventManager,Controller 间会有什么事件飞来飞去就难从机制上把控。代码即文档——能用代码约束的事,最好别只在文档里约束。所以即便底层实现了 QEventManager,作者也不直接暴露它,而是定义具体的 onControllerReset/offControllerReset/fireControllerReset 方法,让架构依赖直观化。

Controller 层变化

各 Controller 使用 Model/View 表

Controller 层 ChangeNotes 表

除去随新交互范式(onControllerReset)和 QShapeStyle 改名带来的连带变更,Controller 层真正的变化是两个:

变化 说明
PropSelectors 变复杂 之前只改 View 的 style;现在还作用于 selection 改其样式,且 selection 变化时自动更新界面
新增 QShapeSelector 支持选择图形,支持删除、移动被选图形

关键洞见:这次迭代证明,目前 M-V-C 的分工能让需求分解非常正交——Model 只管数据结构演进 + 抽象自然业务接口;View 非常稳定、主要做桥接;Controller 各司其职、彼此不受对方需求干扰。

总结

本讲先用三张耦合表把 MVP 版讲透,再做一次真实需求迭代(增删改 + fillColor + 新交互范式),观察各角色对变更的敏感度:变更被很好地隔离在各自层内,分解高度正交。仍是单机版。

先把"是什么"回答清楚

概念 一句话说明
MVP 版 只能增图形,不能删改;View 对 Model 只需 onpaint,Controller 只需 addShape
QShapeStyle 由 QLineStyle 改名而来,把样式收口到一个类,便于后续演进
selection View 变复杂后引入,单选 shape,变化发 onSelectionChanged
onControllerReset Controller 创建完图形发出、Menu 接收,支撑"自动回到 ShapeSelector"的新范式
QEventManager 底层通用事件管理器,但不直接暴露,对外用具体的 on/off/fire 方法

一句话速记

一次需求迭代(增删改 + 填充色 + 新交互范式)证明 M-V-C 分解高度正交;样式收口到 QShapeStyle、事件不暴露通用 EventManager 而用具体方法——代码即文档,能用代码约束就别用文档约束

几条值得记住的判断

  • 代码即文档:能用代码约束的事别只写进文档——所以不暴露通用 QEventManager。
  • 把样式收口到 QShapeStyle 一个类,未来演进就集中可控;对需求演进的推演关键是眼光看多远
  • 弱类型语言重构负担大,作者更偏好静态类型语言(漏改有编译器兜底),但单测不可省。

思考题

文中宁可不暴露通用的 QEventManager,也要定义具体的 on/off/fireControllerReset。回到你的项目:你有没有为了"通用"而暴露了一个万能事件总线,结果谁都能往里塞事件、谁都说不清模块间到底在传什么?该怎么把它收回成几个具体方法?

公告栏
这是我的个人知识库。
记录技术,也记录生活 —— 读过的、试过的、想明白的,都堆在这儿。
最新文章
网站资讯
文章数目 :
5
已运行时间 :
本站总字数 :
15.7k
本站访客数 :
本站总访问量 :
最后更新时间 :
全局知识图谱
当前页面 已访问 文章 标签
ESC 关闭 · 滚轮缩放 · 拖拽移动 · Ctrl+G 开关