MVC模式:模型、视图、控制器
MVC模式,说白了就是「分家」。把数据、界面、逻辑拆成三块,各管各的。我最早接触这个模式是在做Java Swing桌面应用的时候,那时候代码写得乱,一个类里既有数据库查询又有按钮事件,改个界面能把业务逻辑改崩了。后来用了MVC,整个世界清净了。
MVC的三个角色
咱们先看看这三个家伙各自干什么活:
| 角色 | 职责 | 我见过的坑 |
|---|---|---|
| Model(模型) | 封装数据和业务逻辑,不依赖视图和控制器 | 有人把SQL写在视图里,改个字段名要翻遍所有界面文件 |
| View(视图) | 负责展示数据,接收用户输入 | 视图里塞了太多if-else判断状态,代码比业务逻辑还长 |
| Controller(控制器) | 协调模型和视图,处理用户动作 | 控制器太胖,一个方法写了两百行,啥都干 |
核心思想就一句话:视图不直接操作数据,模型不关心你怎么展示。中间人就是控制器。
一个简单的例子:学生成绩管理系统
我习惯用最朴素的例子讲清楚原理。假设我们要做一个学生成绩的展示页面:
Model
// Java
public class Student {
private String name;
private int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
public String getName() { return name; }
public int getScore() { return score; }
// 业务逻辑:判断是否及格
public boolean isPass() {
return score >= 60;
}
}
View
// Java Swing 视图
public class StudentView extends JPanel {
private JLabel nameLabel = new JLabel();
private JLabel scoreLabel = new JLabel();
private JButton refreshButton = new JButton("刷新");
public StudentView() {
add(nameLabel);
add(scoreLabel);
add(refreshButton);
}
public void showStudent(Student student) {
nameLabel.setText("姓名: " + student.getName());
scoreLabel.setText("成绩: " + student.getScore() +
(student.isPass() ? " (及格)" : " (不及格)"));
}
public JButton getRefreshButton() { return refreshButton; }
}
Controller
// Java
public class StudentController {
private Student model;
private StudentView view;
public StudentController(Student model, StudentView view) {
this.model = model;
this.view = view;
// 绑定事件
view.getRefreshButton().addActionListener(e -> updateView());
}
public void updateView() {
view.showStudent(model);
}
public void setStudentScore(int newScore) {
model = new Student(model.getName(), newScore);
updateView();
}
}
你看,模型只管数据,视图只管展示,控制器负责搭桥。改界面?改视图就行。改业务逻辑?改模型就行。互不干扰。
Spring MVC 中的 MVC
Spring MVC 是 Web 开发中最经典的 MVC 实现。我刚开始用 Spring MVC 时,总觉得它和桌面端的 MVC 不太一样。其实本质一样,只是角色换了个马甲:
- Model → 还是 Model,但通常用 POJO 或 Map
- View → JSP、Thymeleaf、FreeMarker 等模板
- Controller → 用 @Controller 注解的类
举个例子:
// Spring MVC Controller
@Controller
public class StudentController {
@GetMapping("/student/{id}")
public String getStudent(@PathVariable Long id, Model model) {
Student student = studentService.findById(id);
model.addAttribute("student", student);
return "studentView"; // 返回视图名称
}
}
这里有个细节:Spring MVC 的 Controller 返回的是视图名称,而不是直接操作视图对象。框架帮你把模型数据塞进视图里。我个人觉得这种设计比 Swing 那种直接操作组件的方式更优雅,解耦更彻底。
Java Swing 中的 MVC
Swing 是桌面端 MVC 的典型代表。不过说实话,Swing 的 MVC 实现得比较「原始」。它用的是「可分离的模型架构」,说白了就是 Model 和 View 之间通过监听器通信。
我记得有一次做一个复杂的表格编辑器,数据变了视图没刷新,排查了半天发现是忘了注册监听器。嗯,这里要注意:Swing 的 MVC 需要手动维护模型和视图的同步。
// Swing 中的模型-视图绑定
public class StudentListModel extends AbstractListModel<Student> {
private List<Student> students = new ArrayList<>();
@Override
public int getSize() { return students.size(); }
@Override
public Student getElementAt(int index) { return students.get(index); }
public void addStudent(Student s) {
students.add(s);
int index = students.size() - 1;
fireIntervalAdded(this, index, index); // 通知视图更新
}
}
// 视图使用
JList<Student> list = new JList<>(new StudentListModel());
看到那个 fireIntervalAdded 了吗?这就是手动通知视图「数据变了,你该刷新了」。Spring MVC 里框架帮你做了这件事,Swing 里你得自己来。
MVC 的核心流程图
下面这张图是我自己画的,把 MVC 的交互流程理清楚了:
MVC 的优缺点
用了这么多年 MVC,我总结一下它的好和不好:
- 解耦彻底:改界面不影响业务逻辑,改业务逻辑不影响界面
- 复用性强:同一个 Model 可以配不同的 View(比如 Web 版和移动端版)
- 便于测试:Model 可以单独做单元测试,不用启动界面
- 团队协作:前端开发改 View,后端开发改 Model,各干各的
- 类数量暴增:一个功能至少三个类,小项目显得臃肿
- 学习曲线:新手容易搞混谁该干什么
- Controller 容易变胖:我见过一个 Controller 里塞了十几个方法,啥都管
- 视图和控制器耦合:在 Swing 里尤其明显,事件监听器经常写在视图里
什么时候用 MVC?
我个人习惯这样判断:
- 项目规模大:多人协作,需要明确分工 → 用 MVC
- 界面多变:同一个数据要展示成表格、图表、卡片等多种形式 → 用 MVC
- 需要自动化测试:业务逻辑需要单独测试 → 用 MVC
- 小工具、原型:就一个页面,改起来也快 → 别用 MVC,直接写一起更省事
你想想看,如果只是写个几百行的脚本,搞三个类来回传数据,那不是自找麻烦吗?模式是工具,不是枷锁。
避坑总结
最后分享几个我踩过的坑:
- 不要把 SQL 写在视图里——我曾经接手过一个项目,JSP 页面里直接写 JDBC 查询,改个数据库字段名要翻遍所有 JSP 文件
- Controller 不要处理业务逻辑——它只负责调度,具体怎么算、怎么查,扔给 Service 层
- Model 不要持有 View 的引用——否则就循环依赖了,改起来想死
- 事件监听要统一管理——在 Swing 里尤其重要,不然监听器满天飞,调试到崩溃
MVC 模式说白了就是「分而治之」的思想。把复杂系统拆成三个维度,每个维度只关心自己的事。这个思想不光用在软件设计里,你想想看,一个公司不也是分部门各司其职吗?