很早就知道有这三个概念,但是一直都不清楚是怎么回事,在网上搜索,都是泛泛而谈,没有具体例子,新手是看不懂的,直到找到这篇文章,我对这三个架构有了更清楚的了解。
从一个简单的例子去研究这三个架构。
注意,MVC,MVP,MVVM中的C,P,VM,下文都要controller指代。
需求如下
界面上显示100,以及两个按钮,其中一个点一下加1,另外一个点一下减1
如图
诚然,这么简单的需求,并不需要用什么架构去完成,可是如果是复杂的需求,要长篇大论才能说完,所以只拿简单的来做例子,实际开发中,你在完成一个需求之前,是需要好好掂量是否要用架构,要的话,用什么架构(不局限于这三个),架构里面又要用什么设计模式等等。经过我的实践,发现,即使是架构改变了,view是可以完全不变的,所以先展现view层的代码。
html部分
<span id="text">100</span> <button id="upBtn">up</button> <button id="downBtn">down</button>
js部分
function $(id) { return document.querySelector(`#${id}`); } function View(controller) { const upBtn = $('upBtn'); const downBtn = $('downBtn'); const textSpan = $('text'); this.render = function(model) { textSpan.innerHTML = model.getValue(); } upBtn.onclick = controller.up; downBtn.onclick = controller.down; }
render方法是核心,方法名称不能改(后面要依赖这个render方法),其中要实现数据的展示逻辑,然后是一些点击事件的绑定
model层
function Model() { let value = 100; this.up = function() { value += 1; }; this.down = function() { value -= 1; }; this.getValue = function() { return value; }; }
保存数据,并提供访问,修改数据的方法,如果仅仅是这样,那么当model改变时,view是不知道的,所以需要让model去通知view,我数据改变了,你要更新了。怎么做呢?利用观察者模式。在model中,增加一个数组views,去保存这个model对应的视图,在修改数据的时候,遍历views数组,调用每个view的render方法,参数是自己。
修改后的model
function Model() { let value = 100; const self = this; const views = []; this.up = function() { value += 1; }; this.down = function() { value -= 1; }; this.getValue = function() { return value; }; this.broadcast = function() { views.forEach(view => view.render(self)); }; this.subscribe = function(cb) { views.push(cb); } }
仔细看修改后的model,虽然增加了通知的方法(broadcast),但是在修改数据的方法(up和down)中并没有去通知视图。这个工作是由controller承担的,另外把view注册到model中,也是controller做的。
controller层
function Controller() { let view = null; let model = null; this.up = function() { // 修改数据 model.up(); // 通知视图 model.broadcast(); }; this.down = function() { model.down(); model.broadcast(); } this.init = function() { view = new View(this); model = new Model(); // 把视图注册到model中 model.subscribe(view); } }
可以看到,controller把自己传给了view去创建视图,同时保存引用,创建model后,把view注册到model中。同时实现了,改变数据,通知视图的工作。
请一定要好好理解MVC,后面的MVP,MVVM都只是稍加修改而已。
在MVC中,改变数据,通知视图,都是在controller做的,注册视图,以及通知视图,这两个方法的实现,都是model完成的,既然model负责数据处理,这两个工作实际上和改变数据是没关系的,把他们都转移到controller中,不仅可以让model层专注于数据处理,同时也方便多个视图共用一个controller
model层
function Model() { let value = 100; this.up = function() { value += 1; }; this.down = function() { value -= 1; }; this.getValue = function() { return value; }; }
model层更小了,删除了注册,通知方法,只保存数据和提供获取,修改数据的方法
controller层
function Controller() { let views = []; let model = null; function broadcast() { views.forEach(view => view.render(model)); } this.up = function() { model.up(); broadcast(); }; this.down = function() { model.down(); broadcast(); } this.init = function() { views.push(new View(this)); model = new Model(); } }
controller,增加了广播方法,该方法的实现和调用都在controller中,另外,如果想多个视图共用一个controller,如果这多个视图都是同一个model,上面代码能够胜任,如果是这多个视图是不同的model,那就要自己去实现好view和model的对应关系了(要用map来存储对应关系,一个数组做不到)。
可以看到,在MVP中,model也有一个up方法,controller也有一个up方法,只是增加了一个广播方法的调用。是不是有些重复呢?把这两个类似的方法整合到controller,model只负责保存数据,不实现修改数据的逻辑,这就是MVVM了,极大地精简model
model层
function Model() { let value = 100; this.getValue = function() { return value; }; this.setValue = function(v) { value = v; } }
其实,不用函数,单纯地用一个变量,也是可以的,但是为了view层不变,view层中依赖model的getValue方法,所以这里还是用函数去实现model
controller层
function Controller() { let views = []; let model = null; function broadcast() { views.forEach(view => view.render(model)); } this.up = function() { model.setValue(model.getValue() + 1); broadcast(); }; this.down = function() { model.setValue(model.getValue() - 1); broadcast(); } this.init = function() { views.push(new View(this)); model = new Model(); } }
精简model的代价是controller要做更多的事情,实现修改数据的逻辑,通知视图。如果用框架,react或者vue,通知视图这部分框架会帮你实现,只要实现数据修改的逻辑就好了。
至此,三个架构都讲完了,如果错误,欢迎讨论。
代码可在github上下载,需要node环境。
参考资料:http://www.cnblogs.com/zhouyangla/p/6936455.html