MVC和MVVM都是一种架构模式,那么为什么会出现它们以及它们的区别是什么呢?把我大致了解的记录一下。
一、为啥出现这些架构模式
起初科学家在设计GUI(图形用户界面时),它的设计模式就是视图(view)、模型(model)和逻辑(应用逻辑,业务逻辑,同步逻辑),用户在键盘鼠标上的操作属于应用逻辑,应用逻辑会触发业务逻辑,然后引起model的变化,model变化后会同步逻辑,再引起视图的变化。
可以看出view和model天生就是分离,剩下的就是该如何处理这些逻辑了,所以出现了这些架构模式,来优化处理这些逻辑。
二、MVC(Model-View-Controller)
用户的一些输入行为由view检测,之后传递给controller执行应用逻辑或业务逻辑,之后model更新,model在通过观察者模式通知view发生改变。
这时model是直接通过观察者模式通知view的,它们之间的耦合度很高,而且一个model可以对应好几个view。
优点:模块化程度高,controller复用性比较高
可以实现一个模型对应多个视图
缺点:view和model之间的耦合度太高了,不利于view的组件化
三、MVP(Model-View-Presenter)
MVP升级了,view和model之间不再之间有来往,view和presenter之间通过接口传递信息,在view上输入的应用逻辑和业务逻辑由presenter处理,再引起model的变化,之后model变更,通过观察者模式告诉presenter哪些数据需要改变,presenter同步逻辑,调用view的接口,使view发生更新。
可以看出presenter要处理的事情变多了,肯定就会比较难以维护了
优点:view和model分开,view可以实现组件化了,presenter易于测试
缺点:presenter维护困难
四、MVVM(Model-View-ViewModel)
MVVM更上一层楼,从图中可以看出它实现了数据的双向绑定,并且不是通过观察者模式,而是发布订阅者模式(这两个有啥区别呢),之前的MVP和MVC里,如果发生数据大量发生变化,往往需要我们手动设置presenter,而在MVVM里数据通过viewmodel里的binder实现view和model的双向绑定,不需要手动设置。
优点:不需要手动设置,自动实现双向绑定,由Binder完成同步逻辑
缺点:性能消耗会增大,维护成本也在增高
五、观察者模式和发布订阅者模式的区别
5.1 观察者模式
观察者(observer)直接订阅主题(subject),当主题被激活时,会触发(fire event)观察者内部的一些事件
//有一家猎人工会,其中每个猎人都具有发布任务(publish),订阅任务(subscribe)的功能
//他们都有一个订阅列表来记录谁订阅了自己
//定义一个猎人类
//包括姓名,级别,订阅列表
function Hunter(name, level){
this.name = name
this.level = level
this.list = []
}
Hunter.prototype.publish = function (money){
console.log(this.level + '猎人' + this.name + '寻求帮助')
this.list.forEach(function(item, index){
item(money)
})
}
Hunter.prototype.subscribe = function (targrt, fn){
console.log(this.level + '猎人' + this.name + '订阅了' + targrt.name)
targrt.list.push(fn)
}
//猎人工会走来了几个猎人
let hunterMing = new Hunter('小明', '黄金')
let hunterJin = new Hunter('小金', '白银')
let hunterZhang = new Hunter('小张', '黄金')
let hunterPeter = new Hunter('Peter', '青铜')
//Peter等级较低,可能需要帮助,所以小明,小金,小张都订阅了Peter
hunterMing.subscribe(hunterPeter, function(money){
console.log('小明表示:' + (money > 200 ? '' : '暂时很忙,不能') + '给予帮助')
})
hunterJin.subscribe(hunterPeter, function(){
console.log('小金表示:给予帮助')
})
hunterZhang.subscribe(hunterPeter, function(){
console.log('小金表示:给予帮助')
})
//Peter遇到困难,赏金198寻求帮助
hunterPeter.publish(198)
//猎人们(观察者)关联他们感兴趣的猎人(目标对象),如Peter,当Peter有困难时,会自动通知给他们(观察者)
5.2 发布订阅者模式
订阅者把自己感兴趣的事件注册到事件调度中心,当发布者发布了该事件后,调度中心再把该消息传递到各个订阅者,订阅者再执行相应的回调函数
//定义一家猎人工会
//主要功能包括任务发布大厅(topics),以及订阅任务(subscribe),发布任务(publish)
let HunterUnion = {
type: 'hunt',
topics: Object.create(null),
subscribe: function (topic, fn){
if(!this.topics[topic]){
this.topics[topic] = [];
}
this.topics[topic].push(fn);
},
publish: function (topic, money){
if(!this.topics[topic])
return;
for(let fn of this.topics[topic]){
fn(money)
}
}
}
//定义一个猎人类
//包括姓名,级别
function Hunter(name, level){
this.name = name
this.level = level
}
//猎人可在猎人工会发布订阅任务
Hunter.prototype.subscribe = function (topic, fn){
console.log(this.level + '猎人' + this.name + '订阅了狩猎' + topic + '的任务')
HunterUnion.subscribe(topic, fn)
}
Hunter.prototype.publish = function (topic, money){
console.log(this.level + '猎人' + this.name + '发布了狩猎' + topic + '的任务')
HunterUnion.publish(topic, money)
}
//猎人工会走来了几个猎人
let hunterMing = new Hunter('小明', '黄金')
let hunterJin = new Hunter('小金', '白银')
let hunterZhang = new Hunter('小张', '黄金')
let hunterPeter = new Hunter('Peter', '青铜')
//小明,小金,小张分别订阅了狩猎tiger的任务
hunterMing.subscribe('tiger', function(money){
console.log('小明表示:' + (money > 200 ? '' : '不') + '接取任务')
})
hunterJin.subscribe('tiger', function(money){
console.log('小金表示:接取任务')
})
hunterZhang.subscribe('tiger', function(money){
console.log('小张表示:接取任务')
})
//Peter订阅了狩猎sheep的任务
hunterPeter.subscribe('sheep', function(money){
console.log('Peter表示:接取任务')
})
//Peter发布了狩猎tiger的任务
hunterPeter.publish('tiger', 198)
//猎人们发布(发布者)或订阅(观察者/订阅者)任务都是通过猎人工会(调度中心)关联起来的,他们没有直接的交流。
5.3 区别
看图最直观的区别就是观察者模式是观察者与目标对象之间直接通信,容易造成代码冗余
发布订阅者模式的订阅者与发布者之间是通过事件调度中心通信的,所以他们之间没有耦合度,而且可以进行一些更细致的操作,如事件调度中心筛选接收到消息的订阅者
5.4 观察者模式到底是不是发布订阅者模式呢
《JavaScript设计模式与开发实践》一书中说了
分辨模式的关键是意图而不是结构
。
如果以结构来分辨模式,发布订阅模式相比观察者模式多了一个中间件订阅器,所以发布订阅模式是不同于观察者模式的;
如果以意图来分辨模式,他们都是实现了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知,并自动更新,那么他们就是同一种模式,发布订阅模式是在观察者模式的基础上做的优化升级。