普通视图

发现新文章,点击刷新页面。
昨天 — 2025年12月21日首页

MVC、MVP、MVVM:用户界面与业务逻辑的解耦

2025年12月21日 16:32

MVC、MVP、MVVM是前端开发中经典的软件架构模式,通过关注点分离实现用户界面与业务逻辑解耦,提升代码可维护性与可扩展性。

一、MVC:分层解耦,实现初步解耦

image.png

核心思想

将代码划分为Model(数据 / 逻辑)、View(界面 / 交互)、Controller(协调 / 控制)三大组件,每个组件有特定的职责

组件责职

组件 职责说明 关键特点
Model 数据管理与业务逻辑。包括数据的访问、处理和存储。 不关心数据展示;当数据发生变化时,通知View更新
View 界面展示与用户交互。从Model获取数据进行展示,同时将用户输入传递给Controller。 一个模型可以对应多个视图,支持不同的展示方式
Controller 协调View和Model。接收用户输入,调用 Model 处理数据,指定View进行渲染 扮演“中间协调者”角色

说明:在Web早期,用户交互主要是用户访问超链接或表单提交,服务器返回新的HTML页面。随着Ajax技术的出现,前后端分离,前端异步请求数据,后端返回JSON数据,由前端进行局部渲染更新页面。

交互流程

  • 用户与View交互,View将用户输入传递给Controller。
  • Controller调用Model处理数据。
  • Model执行完业务逻辑并返回数据。
  • Controller选择View返回给用户,View从Model获取数据生成HTML或JSON

典型框架

  1. Spring MVC

Spring MVC是一个经典的Java Web框架,一个请求处理流程如下:

image.png

  1. Backbone.js

早期的MVC框架主要应用在服务器端,随着前后端分离,前端也有自己的MVC框架。Backbone.js就是其中之一。

在前后端分离的架构下,前端主要做两件事情:视图展示和前后端数据同步,对应Backbone. js中两大组件:Model 和 View。

Model负责数据管理与业务逻辑,并通过RESTful API与后端同步数据。当数据变化时,Model会触发事件通知视图更新。

View监听Model变化,从Model获取数据进行展示;监听DOM事件,接收用户输入并调用Model处理数据。

Backbone.js的View比较厚,承担了MVC中的View和Controller的职责。

// Model定义
var User = Backbone.Model.extend({    
    urlRoot: '/api/users'
});
// View定义
var UserView = Backbone.View.extend({    
    initialize: function() {        
        // 手动监听模型变化        
        this.listenTo(this.model, 'change', this.render);    
    },
    render: function() {       
        // 手动构建HTML或使用模板引擎        
        this.$el.html(`<h1>${this.model.get('name')}</h1>            
                       <p>Email: ${this.model.get('email')}</p>            
                       <button class="edit-btn">编辑</button>`);        
        return this;    
     },
    events: {        
        'click .edit-btn': 'editUser'    
    },
    editUser: function() {  
        // 手动创建编辑视图       
        var editView = new UserEditView({model: this.model});       
        $('#modal-container').html(editView.render().el);    
    }
});
// Controller控制流程
var user = new User({id: 123});
user.fetch();  // 从服务器获取数据
var userView = new UserView({model: user, el: '#user-container'});
userView.render();  // 初始渲染

补充:Backbone.js是早期的SPA开发框架,后来被Vue、React逐步代替。

MVC的优缺点

优点 缺点
修改View/Model互不影响 View依赖Model(需监听Model变化并获取数据)
View和Model支持并行开发 Controller职责过重,需要协调View/Model
一个Model可以对应多个View,支持不同的展示方式。 需要手动同步View和Model
可独立测试 View/Model

二、MVP:Presenter代理,实现彻底解耦

image.png

核心思想

用 Presenter 代替 Controller,彻底切断 View 与 Model 的直接通信。通过Presenter作为双向代理,实现两者的间接通信,解耦更彻底。

组件职责

组件 关键变化
View 仅提供渲染接口给Presenter调用,不需要从Model获取数据进行展示
Model 数据变化通知 Presenter 而非 View
Presenter 接收用户输入 → 调用 Model接口处理数据 → 调用 View 接口更新界面

交互流程

  • 用户与View交互,View调用Presenter接口处理事件。
  • Presenter调用Model处理数据。
  • Model完成后回调Presenter。
  • Presenter调用View接口更新界面。

典型框架

MVP框架主要应用在安卓界面开发,示例代码如下:

class Todo {
    private content: string;
    constructor(content: string) {
        this.content = content;
    }
    getContent(): string {
        return this.content;
    }
}

interface ITodoModel {
    interface TodosCallback {
        onSuccess(): void;
    }
    saveTodo(todo: Todo, callback: GetTodosCallback): void;
}

interface ITodoView {
    showSuccess(): void;
}

interface ITodoPresenter {
    saveTodo(todo: Todo):void;
}

class TodoModel implement ITodoModel {
    saveTodo(todo: Todo, callback: GetTodosCallback): void {
        saveDB(todo);// 模拟保存数据库
        callback.onSuccess();
    }
}

class BasePresenter<T> {
    private mView: T;
    attachView(view: T):void {
        mView = view;
    }
    dettachView():void {
        mView = null;
    }
    getView():T {
        return mView;
    }
}

class TodoPresenter extends BasePresenter<ITodoView> implement ITodoModel.TodosCallbackI, TodoPresenter {
    todoModel: ITodoModel;
    constructor(){
        this.todoModel = new TodoModel();
    }
    saveTodo(todo: Todo):void {
        this.todoModel.saveTodo(todo, this);
    }
    onSuccess() {
        getView().showSuccess();
    }
}

TodoActivity extends AppCompatActivity implements ITodoView {
    private TextView mResultTv;
    private EditText mTodoContentEt;
    private TodoPresenter mTodoPresenter;
    onCreate(Bundle savedInstanceState):void {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_todo);
        initPresenter();
        initView();
    }
    onDestroy(): void {
        super.onDestroy();
        this.mTodoPresenter.dettachView();
     }
    initView():void {
        this.mResultTv = findViewById(R.id.tv_result);
        this.mTodoContentEt = findViewById(R.id.et_todo_content);

        findViewById(R.id.btn_add).setOnClickListener(new View.OnClickListener() {
            onClick(View view):void {
                const todo = getTodoInput();
                this.mTodoPresenter.saveTodo(todo);
            }
        });
     }
     initPresenter() {
         this.mTodoPresenter = new TodoPresenter();
         this.mTodoPresenter.attachView(this);
     }
     getTodoInput(): Todo {
         const content = mTodoContentEt.getText().toString();
         return new Todo(content);
     }
     showSuccess(): void {
         this.mResultTv.setText("success");
     }
}

解读:

  • ITodoModel的职责是数据管理,并通过回调通知ITodoPresenter。
  • ITodoView的职责是展示数据,调用ITodoPresenter接口处理用户输入。
  • ITodoPresenter负责接收用户输入,调用ITodoModel处理数据,然后调用ITodoView展示数据。

MVP的优缺点

优点 缺点
View 与 Model 完全解耦 Presenter职责过重,需要组织View/Model完成所有交互
基于接口通信,进一步解耦,可独立测试 需定义大量接口,增加代码复杂度
View 逻辑纯粹(仅负责渲染) 仍需要手动同步View和Model

三、MVVM:数据绑定,实现自动同步

image.png

核心思想

通过数据绑定实现 View 与 Model 的自动同步,减少手动DOM操作。

组件职责

组件 职责说明
ViewModel 响应式系统:监听 Model 变化并自动同步到 View;监听 DOM 事件并自动更新 Model。
View 声明式地绑定关联的Model,无需手动操作 DOM。
Model 数据管理与业务逻辑,不关系数据展示。

交互流程

  • 用户与View交互,ViewModel自动将用户输入同步给Model。
  • Model执行业务逻辑和操作数据,更新状态。
  • Model变化后,ViewModel自动将Model同步到View。

典型框架

2010年后,Vue.js和React等主流框架采用MVVM架构,减少了DOM操作。

<body>
    <div id="app">
        常见搜索关键字:<span @click="setKeyword('人工智能')">人工智能</span>
        <input
            v-model="keyword" 
            placeholder="请输入要搜索的关键字..." 
            @keyup.enter="search"
        >
        <button @click="search">
            搜索
        </button>
        <div v-if="loading">
            正在搜索中,请稍候...
        </div>
        <div v-for="result in searchResults" :key="result.id">
            <div>{{ result.content }}</div>
        </div>
    </div>
  </body>

  <script>
        const { createApp, ref, computed } = Vue;
        createApp({
            setup() {
                const keyword = ref('');
                const loading = ref(false);
                const searchResults = ref([]);
                // 模拟数据
                const allResults = ref([
                    {
                        id: 1,
                        content: 'Vue.js 入门教程',
                    },
                    {
                        id: 2,
                        content: 'JavaScript 高级编程',
                    },
                ]);
                const setKeyword = (example) => {
                    keyword.value = example;
                    search();
                };
                const search = () => {
                    const searchTerm = keyword.value.toLowerCase();
                    searchResults.value = allResults.value.filter(result => (
                            result.content.toLowerCase().includes(searchTerm));
                    );
                };
                
                return {
                    keyword,
                    setKeyword,
                    loading,
                    searchResults,
                    search,
                };
            }
        }).mount('#app');
    </script>

解读:

  • 在Vue.js中使用v-model指令声明式地实现双向绑定。当用户输入关键字时,数据会自动同步到keyword变量。反之,当用户点击“常见搜索关键字” 按钮,改变keyword变量的值,也会自动同步到输入框。
  • 在Vue.js模版中使用{{}}声明式地实现单向绑定。当执行完搜索逻辑并把结果赋值给searchResults变量,数据会被自动渲染到结果列表。

MVVM的优缺点

优点 缺点
自动同步View和Model,减少手动 DOM 操作 数据绑定可能带来性能开销
ViewModel 可独立测试 双向绑定,调试复杂度高

四、总结

架构 核心思想 优点 缺点 适用场景
MVC 分层解耦 职责分离,易于扩展 View依赖Model 简单应用、后端开发
MVP 代理隔离 彻底切断 View 与 Model 关联 需要手动同步View和Model 中大型应用、需严格测试
MVVM 数据绑定 实现View和Model自动同步 有一定的性能与调试成本 现代前端应用、SPA 开发
❌
❌