博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
MVC\MVP\MVVM的干货实现
阅读量:6861 次
发布时间:2019-06-26

本文共 6827 字,大约阅读时间需要 22 分钟。

原文链接:

为什么会引入这些设计模式?

在没有这些设计模式的野蛮时代,我们是如何写代码的?比如要实现一个这样的需求: 开发一个页面,上面显示一个数值和两个按钮,可以用这两个按钮进行加减操作,操作后的数值会更新显示。

我们的代码是这样的:

          0        复制代码

如果需求改为:再创建一个新页面,要求有类似的逻辑,有两个加减按钮,可以改变视图中某个变量的值,但是要求老页面中的count大于10的话,就不继续增加数值了,新页面中的count小于等于0的话,就不继续减少数值了。

实现这个需求,最笨的方法就是再新建一个HTML文件,把老文件的代码都拷贝过来,按需求改动一下来实现效果。但是这样做的话,项目中的重复代码就很多,而且视图和逻辑没有分离,项目出现问题后也不易排查。如果让我来做这个需求,肯定是把能公用的逻辑抽离出来,让视图和业务逻辑分离开,这样出现问题,也比较好定位。前辈们肯定也是基于此才先后在前端引入了各种设计模式,使我们尽可能的以模块化的思维开发项目。

一、MVC

MVC分为传统MVC和Web MVC,Web MVC又分为前端MVC和后端MVC,本文只针对Web 前端MVC来做说明。

先把原理图放出来:

先简单说下原理,接下来看各个部分的代码实现,建议看完代码实现后,再回头来看下这段原理介绍。

一个View对应一个Controller,一个Model可以对应多个View。用户在View视图层的输入会触发controller的调用,比如用户在视图上点击“+”按钮,controller得到事件触发后,来决定调用Model的逻辑改变数据,Model和View层通过发布-订阅的模式相连接,Model层数据变化后,会通知所有订阅的视图重新执行渲染操作。整个过程就是:用户触发了点击事件,事件改变了数据,数据变化会通知页面,页面拿到新数据,重新渲染。

Controller

根据上面的需求,会有两个视图,因此我们就创建两个controller​// 对应视图1的controllerfunction Controller1(model) {  this.model = model}​Controller1.prototype.onAddCount = function(count) {  if (count > 10) return  this.model.increase(count)}​Controller1.prototype.onDecCount = function(count) {  this.model.decrease(count)}​module.exports = Controller1​// 对应视图2的controllerfunction Controller2(model) {  this.model = model}​Controller2.prototype.onAddCount = function(count) {  this.model.increase(count)}​Controller2.prototype.onDecCount = function(count) {  if (count <= 0) return   this.model.decrease(count)}module.exports = Controller2复制代码

View

//视图1function View1(controller, model) {  this.controller = controller  this.model = model  this.model.register(this) //收集视图1  this.template = compile($("#view1").html(this.model.getVal()))  this.$el = $("
")}​View1.prototype.build = function() { this.render() this.listen()}​View1.prototype.render = function() { this.$el.html(this.template())}​View1.prototype.listen = function() { var self = this this.$el .find("view1.increase-btn") .on("click", function() { var num = $('view1.count').val() self.controller.onAddCount(num) }) this.$el .find("view1.decrease-btn") .on("click", function() { var num = $('view1.count').val() self.controller.onDecCount(num) })}​module.exports = View1​​//视图2function View2(controller, model) { this.controller = controller this.model = model this.model.register(this) //收集视图2 this.template = compile($("#view2").html(this.model.getVal())) this.$el = $("
")}​View2.prototype.build = function() { this.render() this.listen()}​View2.prototype.render = function() { this.$el.html(this.template())}​View2.prototype.listen = function() { var self = this this.$el .find("view2.increase-btn") .on("click", function() { var num = $('view2.count').val() self.controller.onAddCount(num) })​ this.$el .find("view2.decrease-btn") .on("click", function() { var num = $('view2.count').val() self.controller.onDecCount(num) })}​module.exports = View2复制代码

Model

function Model() {  this.count = 5  var self = this, views = []  // 发布订阅模式  this.register = function(view) {    views.push(view);  }​  this.notify = function() {    for(var i = 0; i < views.length; i++) {        views[i].render(self);    }  }​  this.increase = function () {    this.count = this.count + 1    this.notify()  }  this.decrease = function () {    this.count = this.count - 1    this.notify()  }  this.getVal = function () {    return this.count  }}​module.exports = Model复制代码

初始化这个应用

function initApp() {  var model = new Model  var controller1 = new Controller(model)  var view1 = new View1(controller1, model)  view1.build()​  var controller2 = new Controller(model)  var view2 = new View2(controller1, model)  view2.build()}​initApp()复制代码

到此,一个采用MVC架构的web应用就完成了,可以看到业务逻辑层和视图层拆分开了,但是一个视图层绑定了一个控制层,如果我想要复用view的话,该怎么办呢?要解决这个问题,就引入了MVP设计模式。

二、MVP

mvp是mvc的改良版,它把view和model隔离开了,先上原理图:

用户点击页面上的按钮,触发绑定在view上的点击事件,调用presenter的业务逻辑,presenter收到调用通知后,去调用model层处理数据,然后再调用view层更新视图,我们把上面的例子简化,专注于各层之间的调用关系。

View

function View() {  this.presenter = null  this.template = compile($("#view1").html())  this.$el = $("
")}​View.prototype.build = function() { this.render() this.listen()}​View.prototype.render = function() { this.$el.html(this.template())}​View.prototype.setPresenter = function(presenter) { this.presenter = presenter}​View.prototype.listen = function() { var self = this this.$el .find("view.increase-btn") .on("click", function() { var num = $('view.count').val() //看到这里你可能会有疑问,在初始化View构造函数的时候,并没有传入presenter对象,又如何调用presenter的onAddCount方法呢?可以耐心看到下面,其实在初始化Presenter的时候,调用了view对象的setPresenter方法,从而把presenter对象的属性和方法绑定到view对象的私有属性presenter上。因此可以看出在MVP模式中,view模块是相对独立的,好处是view模块可以复用,对view的操作和对model的调用全部放在了presenter层,当业务庞大的时候,这一层会很fat,这也是为什么后面又引入了MVVM模式。 self.presenter.onAddCount() })}​View.prototype.getVal = function() { return this.$el.find("num").val()}​View.prototype.setVal = function(content) { return this.$el.find("num").val(content)}​module.exports = View复制代码

Presenter

function Presenter(view, model) {  this.view = view  this.model = model  this.init()}​Presenter.prototype.init = function() {  this.view.setPresenter(this)  this.view.build()}​Presenter.prototype.onAddCount = function() {  var count = this.view.getVal()  if (count > 10) return  //presenter调用model处理逻辑,然后把新数据传递给view,重新渲染视图  this.model.increase(count)  this.view.setVal(this.model.getValue())}​module.exports = Presenter复制代码

Model

function Model() {  this.count = 5​  this.increase = function (num) {    this.count = this.count + num  }​  this.getValue = function () {    return this.count  }}​module.exports = Model复制代码

初始化应用

function initApp() {  var view = new View  var model = new Model  var presenter = new Presenter(view, model)}​initApp()复制代码

从初始化的代码也可看出,model和view互相没有直接联系,这使得他们可以作为独立模块来复用。其中Presenter起到一个中枢神经的作用,业务逻辑主要放在了这一层,因此这一层也会比较臃肿。

三、MVVM

MVVM是目前前端领域最流行的模式,最著名的Vue就是MVVM架构的。

Model层可以被称为数据层,因为model不关心业务逻辑,只关注数据本身,也可以把model理解成一个类似于json的数据对象

Model

var data = {    num: 5}复制代码

View层是通过模板语法,声明式的把数据渲染到视图中

{
{ num }}
复制代码

ViewModel类比于MVP中的Presenter,与之不同的是,在ViewModel中不需要开发者在开发的时候显示的调用view对象的渲染接口。采用MVVM架构的框架会在初始化ViewModel的时候,应用数据劫持+发布-订阅模式实现数据和视图的响应式变化(这部分详见Vue的框架原理)。也就是model中的数据改变了,会触发view的更新渲染。view层的数据变化,也会引起model中数据的变化。

new ViewModel({    el: '#myapp',    data: data,    methods: {        increase(v) {            if(this.val < 10) {                this.val += v;            }        },        decrease(v) {            if(this.val > 0) {                this.val -= v;            }        }    }});复制代码

可以看到实现了MVVM架构的框架帮我们封装了需要手动调用的逻辑,使各个模块的开发更独立,简化了开发流程,工程师可以用更短的时间开发出更好维护的项目,人类真是为了可以心安理得的懒惰,而无所不用其极,也应了那句名言:

懒惰才是第一生产力        ___伊丽莎白.老仙女复制代码

总结

三种很抽象的设计模式,用代码实现一遍就很好理解了,工程师的世界,知行合一才是进步的最短路径。

若有不同的理解,欢迎提issue。

参考资料:

转载于:https://juejin.im/post/5bf8e898f265da611e4d510e

你可能感兴趣的文章
React同构踩坑记录
查看>>
教你用Python如何实现微信自动回复功能,机器人自动对话!
查看>>
使用var定义变量和不使用的区别
查看>>
React两个bug踩坑
查看>>
vue引入mxGrpah
查看>>
合并冲突 - 每天三分钟玩转Git(三)
查看>>
你们公司今年会发年终奖吗?Python告诉你大家怎么说
查看>>
Derek解读Bytom源码-Api Server接口服务
查看>>
Java之JDK7的新语法探索
查看>>
微软大秀Windows 10中的MyOffice App免费功能
查看>>
UDP协议
查看>>
学jstl,看这一篇就够了
查看>>
Webpack之tapable深入学习(一)--Sync*Hook
查看>>
Redis 环境配置,缓存必备
查看>>
设计模式 系列记忆之 六大设计原则
查看>>
写给即将面试的你
查看>>
Android NDK开发之JNI基础
查看>>
Java程序员有话说 大专生毕业 6 年月薪 3W+:不从众也不普通
查看>>
D2 日报 2019年5月29日
查看>>
剑指Offer(java答案)(11-20)
查看>>