9、MVC变体:MVP模式:MVP(Model-View-Presenter)模式的定义、与MVC的区别以及适用场景

聊完MVC,咱们得聊聊它的一个“亲兄弟”——MVP。说实话,我在早期做Android开发时,跟MVP打交道的时间比MVC还多。那时候MVC在移动端经常搞得一团糟,Activity既当View又当Controller,代码膨胀得让人头疼。后来引入MVP,才算是把这块硬骨头啃下来了。

9.1 MVP模式的定义

MVP,全称Model-View-Presenter。它把应用拆成三块:

  • Model:数据层,跟MVC里的Model一样,负责数据获取、存储和业务逻辑。
  • View:视图层,负责UI展示和用户交互。但注意,这里的View比MVC里的View更“薄”——它只做展示,不做决策。
  • Presenter:中间人,负责从Model拿数据,然后“指挥”View去渲染。它不直接操作DOM或UI控件,而是通过接口跟View通信。

说白了,MVP的核心思想就是:View只负责“长什么样”,Presenter负责“怎么干”。View和Model完全解耦,谁也不认识谁。

核心区别一句话总结:MVC里View可以偷偷看Model一眼,MVP里View连Model的门朝哪开都不知道。

9.2 MVP与MVC的区别

我经常被问到:“MVP和MVC到底差在哪?” 嗯,这个问题其实挺关键的。咱们直接上对比表:

对比维度 MVC MVP
View与Model的关系 View可以直接访问Model(读数据) View完全不碰Model,全靠Presenter中转
Controller/Presenter职责 Controller接收用户输入,更新Model Presenter既处理用户输入,又负责更新View
View的“智商” View可以包含一些展示逻辑 View几乎为零逻辑,纯被动渲染
测试友好度 中等(View依赖Model,不好单独测) 极高(Presenter可脱离UI单独测试)
典型应用场景 Web后端、传统桌面应用 Android开发、复杂前端组件

你想想看,MVC里View可以直接读Model,这在Web场景下问题不大——反正浏览器端渲染,Model就是数据对象。但在移动端或桌面端,View往往跟UI框架深度绑定,如果View直接依赖Model,那测试就变得非常痛苦。我曾经在一个Android项目里,因为Activity直接操作数据库,导致单元测试跑不起来——每次测试都得启动模拟器,慢得让人抓狂。

MVP解决了这个问题。Presenter不依赖任何UI框架,你可以用纯JUnit测试它。View只需要提供一个接口实现,测试时传个Mock进去就行。

9.3 MVP的工作流程

咱们用一张图来理解MVP的协作方式:

Model 数据获取 / 存储 / 业务 View UI展示 / 用户交互 Presenter 业务编排 / 状态管理 用户事件 更新UI指令 请求数据 返回数据 MVP 数据流:用户操作 → Presenter → Model → Presenter → View View 与 Presenter 通过接口绑定

流程其实很清晰:

  1. 用户点击按钮,View把事件转发给Presenter。
  2. Presenter调用Model获取数据。
  3. Model返回数据给Presenter。
  4. Presenter处理数据,调用View的接口方法更新UI。

注意看,View和Model之间没有直接连线。这就是MVP最核心的设计——单向依赖。Presenter是唯一的“话事人”。

9.4 代码示例:一个登录模块

光说不练假把式。咱们写个登录模块的MVP实现,你就明白它怎么用了。

// 1. 定义View接口
public interface LoginView {
    void showLoading();
    void hideLoading();
    void onLoginSuccess(String username);
    void onLoginError(String errorMsg);
}

// 2. 定义Presenter
public class LoginPresenter {
    private LoginView view;
    private UserModel model;
    
    public LoginPresenter(LoginView view) {
        this.view = view;
        this.model = new UserModel();
    }
    
    public void login(String username, String password) {
        view.showLoading();
        // 模拟异步请求
        new Thread(() -> {
            boolean result = model.checkLogin(username, password);
            // 切回主线程更新UI
            if (result) {
                view.onLoginSuccess(username);
            } else {
                view.onLoginError("用户名或密码错误");
            }
            view.hideLoading();
        }).start();
    }
}

// 3. View实现(Android Activity示例)
public class LoginActivity extends Activity implements LoginView {
    private LoginPresenter presenter;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        presenter = new LoginPresenter(this);
        
        findViewById(R.id.btn_login).setOnClickListener(v -> {
            String user = getUsername();
            String pwd = getPassword();
            presenter.login(user, pwd);
        });
    }
    
    @Override
    public void showLoading() { /* 显示进度条 */ }
    @Override
    public void hideLoading() { /* 隐藏进度条 */ }
    @Override
    public void onLoginSuccess(String username) { /* 跳转主页 */ }
    @Override
    public void onLoginError(String msg) { /* 显示错误提示 */ }
}

我的经验:写MVP时,View接口的方法粒度要适中。太细了(比如setText、setColor各一个方法)会导致接口爆炸;太粗了(一个refreshAll方法)又失去了MVP的解耦优势。我一般按“业务意图”来划分——比如showLoading、onLoginSuccess这种,一看就知道是干嘛的。

9.5 适用场景

MVP不是万能的,它有自己的“舒适区”。我总结了几类适合用MVP的场景:

  • 复杂UI交互:比如表单校验、多步骤向导。Presenter可以集中管理状态,View只管渲染。
  • 需要频繁单元测试:如果你对代码质量要求高,MVP的Presenter可以脱离UI跑测试,爽得很。
  • 多端复用:同一套Presenter逻辑,可以对接Web端、移动端、桌面端——只要各自实现View接口就行。
  • 团队协作:UI设计师改View,后端改Model,Presenter由核心开发维护,互不干扰。

避坑指南:我曾经在一个小工具项目里强行用MVP,结果代码量翻了一倍。MVP的接口定义和Presenter逻辑确实会增加代码量。如果你的应用逻辑简单(比如纯展示页面),用MVC甚至直接写都行,别为了模式而模式。

9.6 MVP的优缺点

咱们客观一点,MVP有它的好,也有它的“坑”:

优点 缺点
View和Model完全解耦,可替换性强 代码量增加,需要定义大量接口
Presenter可单元测试,不依赖UI环境 Presenter可能变得臃肿(上帝类)
职责清晰,团队协作效率高 View接口变更时,Presenter和实现类都得改
适合复杂交互和状态管理 简单场景下显得“杀鸡用牛刀”

嗯,这里要注意一点:Presenter臃肿的问题。我见过一些项目,Presenter里塞了几千行代码,什么逻辑都往里放。这时候就该考虑拆分了——比如拆成多个小的Presenter,或者引入UseCase层。

9.7 什么时候别用MVP

说实话,MVP不是银弹。以下情况我建议你慎重:

  • 原型开发阶段:快速验证想法时,MVP的接口定义会拖慢节奏。
  • 简单CRUD页面:就一个列表展示,加个搜索框,用MVP纯属自找麻烦。
  • 团队不熟悉:如果团队里没人写过MVP,强行推广会导致代码风格混乱。我见过有人把MVP写成“MVC换皮”——Presenter里直接操作DOM,那还不如用MVC呢。

最后说一句:模式是工具,不是信仰。MVP也好,MVC也罢,选哪个取决于你的场景和团队。我个人习惯是:复杂交互用MVP,简单展示用MVC,数据驱动用MVVM。没有最好的模式,只有最合适的。


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