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 那种直接操作组件的方式更优雅,解耦更彻底。

小技巧:在 Spring MVC 中,我习惯把 Controller 写得尽量薄。一个方法只做一件事:接收参数、调用服务、返回视图。业务逻辑全部扔到 Service 层去。

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 里你得自己来。

避坑指南:我曾经在 Swing 项目里忘记调用 fireXxx 方法,结果数据改了界面纹丝不动。调试了半天才发现是监听器没触发。建议封装一个 BaseModel,把 fire 方法统一管理。

MVC 的核心流程图

下面这张图是我自己画的,把 MVC 的交互流程理清楚了:

MVC 交互流程 Controller 接收用户输入 更新 Model 选择 View Model 封装数据 业务逻辑 通知视图更新 View 展示数据 接收用户操作 发送事件到 Controller 用户操作 更新数据 通知更新 事件回调 流程:用户操作 → Controller 处理 → 更新 Model → Model 通知 View 刷新 虚线表示事件回调,实线表示主动调用 Controller Model View

MVC 的优缺点

用了这么多年 MVC,我总结一下它的好和不好:

优点:
  • 解耦彻底:改界面不影响业务逻辑,改业务逻辑不影响界面
  • 复用性强:同一个 Model 可以配不同的 View(比如 Web 版和移动端版)
  • 便于测试:Model 可以单独做单元测试,不用启动界面
  • 团队协作:前端开发改 View,后端开发改 Model,各干各的
缺点:
  • 类数量暴增:一个功能至少三个类,小项目显得臃肿
  • 学习曲线:新手容易搞混谁该干什么
  • Controller 容易变胖:我见过一个 Controller 里塞了十几个方法,啥都管
  • 视图和控制器耦合:在 Swing 里尤其明显,事件监听器经常写在视图里

什么时候用 MVC?

我个人习惯这样判断:

  • 项目规模大:多人协作,需要明确分工 → 用 MVC
  • 界面多变:同一个数据要展示成表格、图表、卡片等多种形式 → 用 MVC
  • 需要自动化测试:业务逻辑需要单独测试 → 用 MVC
  • 小工具、原型:就一个页面,改起来也快 → 别用 MVC,直接写一起更省事

你想想看,如果只是写个几百行的脚本,搞三个类来回传数据,那不是自找麻烦吗?模式是工具,不是枷锁。

避坑总结

最后分享几个我踩过的坑:

  1. 不要把 SQL 写在视图里——我曾经接手过一个项目,JSP 页面里直接写 JDBC 查询,改个数据库字段名要翻遍所有 JSP 文件
  2. Controller 不要处理业务逻辑——它只负责调度,具体怎么算、怎么查,扔给 Service 层
  3. Model 不要持有 View 的引用——否则就循环依赖了,改起来想死
  4. 事件监听要统一管理——在 Swing 里尤其重要,不然监听器满天飞,调试到崩溃

MVC 模式说白了就是「分而治之」的思想。把复杂系统拆成三个维度,每个维度只关心自己的事。这个思想不光用在软件设计里,你想想看,一个公司不也是分部门各司其职吗?

公众号:蓝海资料掘金营,微信deep3321