第四章:上位机架构设计——MVC模式在烧录工具中的应用
做烧录工具开发这些年,我踩过最大的坑就是——代码写成一坨,改一个功能要动三个文件。后来我学乖了,老老实实用MVC模式。说白了,MVC就是把代码分成三块:数据、界面、控制逻辑。各管各的,互不干扰。
4.1 MVC模式:烧录工具的分层哲学
MVC不是什么高深理论,它就是一套分工规则。我习惯这样划分:
- Model(模型):负责数据。比如固件文件内容、烧录进度、设备状态。
- View(视图):负责界面。按钮、进度条、日志显示区,只管展示。
- Controller(控制器):负责调度。用户点了“开始烧录”,控制器去调Model读文件,再调View更新进度。
你想想看,如果这三块混在一起会怎样?我见过一个项目,烧录按钮的点击事件里直接写了串口通信代码。后来要换通信协议,整个界面都得重写。嗯,这就是没有分层的代价。
核心原则:Model不碰界面,View不碰数据,Controller只做调度。
4.2 模块化设计:拆开才能复用
模块化设计,说白了就是“高内聚、低耦合”。我一般把烧录工具拆成这样几个模块:
| 模块名称 | 职责 | 依赖 |
|---|---|---|
| 通信模块 | 串口/USB/网络通信 | 无 |
| 协议解析模块 | 打包/解包烧录协议 | 通信模块 |
| 固件管理模块 | 读取/校验/缓存固件 | 无 |
| 烧录引擎模块 | 控制烧录流程 | 协议解析、固件管理 |
| 日志模块 | 记录运行信息 | 无 |
每个模块只做一件事。通信模块只管收发字节,别去管固件内容对不对。我曾经遇到一个项目,通信模块里偷偷做了数据校验,结果换芯片型号时校验逻辑全得改。这就是耦合的代价。
我的习惯:每个模块提供一个接口类,其他模块只通过接口调用。这样换实现就像换插头一样简单。
4.3 事件驱动架构:别让代码“死等”
烧录工具最怕什么?界面卡死。用户点一下“开始烧录”,整个界面不动了,进度条也不走。这通常是同步调用惹的祸。
事件驱动架构就是来解决这个问题的。核心思想:
- 烧录过程产生事件(如“数据发送完成”、“收到应答”、“校验失败”)
- 事件被放入队列
- 主循环或事件处理器依次处理
- 界面只负责监听事件,不参与烧录逻辑
我举个例子:
// 事件定义
enum EventType {
DATA_SENT,
ACK_RECEIVED,
FLASH_VERIFIED,
ERROR_OCCURRED
};
// 事件处理器
void onEvent(Event event) {
switch(event.type) {
case DATA_SENT:
updateProgressBar(event.progress);
break;
case ACK_RECEIVED:
sendNextPacket();
break;
case ERROR_OCCURRED:
showErrorDialog(event.message);
break;
}
}
这样做的好处是:界面永远不会卡。烧录在后台跑,界面该刷新刷新,该响应用户操作就响应。我曾经用同步方式写过一个烧录工具,烧录大固件时界面卡了30秒,用户以为程序崩溃了。换成事件驱动后,再也没人投诉了。
注意:事件处理函数里不要做耗时操作。如果需要,另开线程或使用异步任务。否则事件队列会被堵住,跟同步调用没区别。
4.4 日志系统设计:出问题时的救命稻草
日志系统,很多人觉得可有可无。但真正做过产品的人都知道——没有日志,出了问题你连查的方向都没有。
我设计日志系统时,一般分四个级别:
| 级别 | 用途 | 示例 |
|---|---|---|
| DEBUG | 开发调试用 | “发送第100个包,长度1024” |
| INFO | 正常流程记录 | “烧录开始,固件大小1MB” |
| WARNING | 异常但不影响运行 | “校验和重试第2次” |
| ERROR | 严重错误 | “通信超时,设备无响应” |
日志系统还要支持输出到多个目标:
- 界面显示:用户能看到实时状态
- 文件记录:保存到本地,方便事后分析
- 网络发送:远程调试时用
我曾经遇到一个现场问题,客户说烧录偶尔失败。我远程连不上设备,就让客户把日志文件发给我。一看日志,发现是USB线太长导致通信不稳定。如果没有日志系统,这个问题根本没法定位。
建议:日志文件要带时间戳和滚动机制。比如每天一个文件,保留最近7天。不然日志文件越来越大,最后磁盘满了,程序自己先挂了。
4.5 架构总览:一张图说清楚
下面这张图展示了烧录工具的完整架构。我习惯用SVG画这种图,清晰又不用加载外部资源。
这张图里,View、Controller、Model三层各司其职。箭头表示数据流向,都是单向的。这样做的好处是:改界面不影响数据,改数据不影响界面。我见过太多项目,改一个按钮要动三个文件,就是因为没分层。
4.6 避坑指南:我踩过的那些坑
做架构设计这么多年,有些坑我替你们踩过了:
- 别在View里写业务逻辑:我曾经把烧录流程写在按钮点击事件里,后来要加一个“自动烧录”功能,代码根本没法复用。
- 事件队列要设上限:如果事件生产速度大于消费速度,队列会无限增长。我一般设1000个事件的上限,超了直接丢弃旧事件。
- 日志别用同步写文件:日志写文件是IO操作,会卡住主线程。我习惯用异步日志,先写到内存缓冲区,后台线程再刷到磁盘。
- 模块接口要稳定:接口一旦发布,就别轻易改。我吃过一次亏,改了通信模块的接口,结果三个依赖模块全得跟着改。
重要提醒:架构设计不是一次性的。随着需求变化,架构也要迭代。但核心原则不变——高内聚、低耦合、单向依赖。记住这三点,你的代码至少能少改一半。
好了,这一章的内容就到这里。架构设计是烧录工具的骨架,骨架搭好了,后面加功能就像搭积木一样简单。下一章我们聊聊具体的通信协议实现,到时候我会分享一些实际项目中的协议设计经验。