浅析MVC、MVP、MVVM 框架实现

117 阅读4分钟

ps:开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第4天,点击查看活动详情

复杂的软件必须有清晰合理的架构,否则无法开发和维护。为了将业务和视图的实现代码分离,目前比较流行三种前端架构:

  • MVC ( Model-View-Controller)

  • MVP (Model-View-Presenter)

  • MVVM( Model-View-ViewModel)

Model 为模型层,主要管理业务模型的数据和行为;View 为展示层,其职责就是管理用户界面。

三个架构模式目的都是为了解耦 Model 和 View,主要不同点就在于三者实现解耦的方案不同。

1、MVC

一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。MVC被独特的发展起来用于映射传统的输入、处理和输出功能在一个逻辑的图形化用户界面的结构中。 简单来说就是通过controller的控制去操作model层的数据,并且返回给view层展示,当用户出发事件的时候,view层会发送指令到controller层,接着controller去通知model层更新数据,model层更新完数据以后直接显示在view层上,这就是MVC的工作原理。 如下图:

img

代码实现如下:

​
<select id="animal">
    <option value="chick">chick</option>
    <option value="hen">hen</option>
    <option value="cock">cock</option>
</select>
<div><span id="name"></span>'s voice is <span id="voice"></span>.</div>
var M = {
    voices : {
        chick : 'bi bi',
        hen : 'gu gu',
        cock : 'wo wo'
    },
    name : '',
    voice : '',
    change : function(name){
        this.name = name;
        this.voice = this.voices[name];
        V.update();//调用V
    },
    get : function(k){
        return this[k];
    }
};
 
var V = {
    init : function(){
        document.querySelector('#animal').onchange= function(){
            C.set(this.value);//调用C
        };
    },
    update : function(){
        document.querySelector('#name').textContent = M.get('name');
        document.querySelector('#voice').textContent = M.get('voice');
    }
};
 
var C = {
    init : function(){
        V.init();
    },
    set : function(name){
        M.change(name);//调用M
    }
};
 
C.init();

以上代码很清晰分出M、V和C三个模块,V通过事件通知C控制M的变化,M变化后会调用V进行视图更新,整个流程是单向的。

2、MVP

MVP 是从经典的模式MVC演变而来,它们的基本思想有相通的地Controller/Presenter负责逻辑的处理,Model提供数据,View负责显示。

各部分之间的通信,都是双向的。;View 与 Model 不发生联系,都通过 Presenter 传递;View 非常薄,不部署任何业务逻辑,称为"被动视图"(Passive View),即没有任何主动性,而 Presenter非常厚,所有逻辑都部署在那里。如下图:

img

代码实现如下:

​
<select id="animal">
    <option value="chick">chick</option>
    <option value="hen">hen</option>
    <option value="cock">cock</option>
</select>
<div><span id="name"></span>'voice is <span id="voice"></span>.</div>
var M = {
    voices : {
        chick : 'bi bi',
        hen : 'gu gu',
        cock : 'wo wo'
    },
    name : '',
    voice : '',
    change : function(name){
        this.name = name;
        this.voice = this.voices[name];
    },
    get : function(k){
        return this[k];
    }
};
 
var V = {
    init : function(){
        document.querySelector('#animal').onchange = function(){
            C.set(this.value);//调用C
        };
    },
    update : function(kv){
        for(k in kv){
            document.querySelector('#'+k).textContent = kv[k];
        }
    }
};
 
var P = {
    init : function(){
        V.init();//调用V
    },
    set : function(name){
        M.change(name);//调用M
        V.update({
            name : M.get('name'), 
            voice : M.get('voice')
        });//调用V
    }
};
 
P.init();

代码来看跟MVC差不太多,区别主要在于M和V没有直接交互,而是通过P来进行完全控制,所以P也被称为控制狂,任何交互上的事情都由很强的控制欲。

3、MVVM

MVVM采用双向绑定(data-binding):View的变动,自动反映在 ViewModel,反之亦然。AngularJS 和 Vue 都采用这种模式,而React采用了单向数据流,属于MVP架构。  

要实现数据绑定,通常都采用发布者-订阅者模式进行实现,但这部分工作如果由开发人员自己来写代码实现,其实还是挺复杂的,因此,各大平台都提供了各自的内部实现。比如 Vue 和 AngularJS 自身都实现了数据绑定,Android 目前最主流的方案就是采用 Jetpack,iOS 最常用的方案则是结合 ReactiveCocoa(RAC)实现。

MVP 和 MVVM 都是为了解决界面和数据的分离问题,两者只是采用了不同的实现方案。MVP 之间的交互主要是通过接口实现的,其主要弊端就是需要编写大量接口。而 MVVM 则是通过数据绑定的方式实现交互,虽然其实现需要依赖具体的一些框架工具,但明显大大减少了开发者需要编写的代码量。

img

代码实现如下:

​
<div id="content">
    <select id="animal" data-bind="change:doChange">
        <option value="chick">chick</option>
        <option value="hen">hen</option>
        <option value="cock">cock</option>
    </select>
 
    <div><span data-bind="name"></span>'voice is <span data-bind="voice"></span>.</div>
</div>
var M = {
    voices : {
        chick : 'bi bi',
        hen : 'gu gu',
        cock : 'wo wo',
    },
    name : '',
    voice : ''
    doChang : function(){
        M.name = this.value;
        M.voice = this.voices[obj.name];
    }
};
 
var V = {   
    bindEvent : function(el, evt, func){
        el['on'+evt] = M[func]();
    },
    updateText : function(el, text){
        el.textContent = text;
    }
};
 
var VM = {
    init : function(rootSelector){
        this.observer();
        this.compiler(rootSelector);
    },
    paths : {},
    observer : function(){
        var _this = this;
        for(var k in M){
            (function(k){
                var name = k, value = M[k];
                Object.defineProperty(M, name, {
                    get : function(){
                        return value;
                    },
                    set : function(v){
                        value = v;
                        V.updateText(_this.paths[name], v);
                    }
                });
            })(k);
        }
        
    },
    compiler : function(rootSelector){
        var binds = document.querySelector(rootSelector).querySelectorAll('[data-bind]');
        for(var i=0, len=binds.length;i<len;i++){
            var el = binds[i];
            var directive = el.getAttribute('data-bind');
            if(directive.indexOf(':')>-1){//事件指令
                var directives = directive.split(':');
                var evt = directives[0], func = directives[1];
                V.bindEvent(el, evt, func);
            }else{//文本指令
                this.paths[directive] = el;
                V.updateText(el, M[directive]);
            }
        }
    }
};
 
VM.init('#content');

MVC/MVP/MVVM的原理以及代码实现,文章到这里就差不多结束了。

小结

从 MVC 架构模式到 MVVM,从分离展示层到展示模型层,经过几十年的发展和演变,MVC 架构模式出现了各种各样的变种,并在不同的平台上有着自己的实现。

MVC/MVP/MVVM都是根据不同的应用需求和应用环境逐步发展而来的,它们都有各自优缺点和适用环境。比如Web开发中因为要跨越网络通讯,如果使用MVP或者MVVM来实现代价是巨大的,因为目前来说网络数据很珍贵的资源。