模型-视图-展示器(MVP)是MVC设计模式的一个衍生模式,它专注于提升展现逻辑.它来自于上个世纪九十年代早期的一个叫做Taligent的公司,当时他们正工作于一个基于C++ CommonPoint环境的模型.而MVC和MVP的目标都直指对整个多组件关注点的分离,它们之间有一些基础上的不同。
为了要做出总结的目的,我们将专注于最适合于基于Web架构的MVP版本。
MVP中的P代表展示器.它是一个包含视图的用户界面逻辑的组件.不像MVC,来自视图的调用被委派给了展示器,它是从视图中解耦出来的,并且转而通过一个接口来同它进行对话.这允许所有类型的有用的东西,比如在单元测试中模拟视图的调用。
对MVP最通常的实现是使用一个被动视图(Passive View 一种对所有动机和目的保持静默的视图),包含很少甚至与没有任何逻辑.如果MVC和MVP是不同的,那是因为其C和P干了不同的事情.在MVP中,P观察着模型并且当模型发生改变的时候对视图进行更新.P切实的将模型绑定到了视图,这一责任在MVC中被控制器提前持有了。
通过视图发送请求,展示者执行所有和用户请求相关的工作,并且把数据返回给视图。从这个方面来讲,它们获取数据,操作数据,然后决定数据如何在视图上面展示。在一些实现当中,展示者同时和一个服务层交互,用于持久化数据(模型)。模型可以触发事件,但是是由展示者扮演这个角色,用于订阅这些事件,从而来更新视图。在这个被动体系架构下,我们没有直接数据绑定的概念。视图暴露setter ,而展示者使用这些setter 来设置数据。
相较于MVC模式的这个改变所带来的好处是,增强了我们应用的可测试性,并且提供了一个更加干净的视图和模型之间的隔离。但是在这个模式里面伴随着缺乏数据绑定支持的缺陷,这就意味着必须对这个任务做另外的处理。
尽管被动视图实现起来普遍都是为视图和实现一个接口,但在它之上还是有差异的,包括可以更多的把视图从展示器解耦的事件的使用。由于在Javascript中我们并没有接口的构造,我们这里更多的是使用一种约定而不是一个明确的接口。技术上看它仍然是一个接口,而从那个角度对于我们而言把它作为一个接口引用可能更加说得过去一些。
也有一种叫做监督控制器的MVP的变种,它更加接近于MVC和MVVM模式,因为它提供了来自于直接来源于视图的模型的数据绑定。键值观察(KVO)插件(比如Derick Bailey的Backbone.ModelBingding插件)趋向于吧Backbone带出被动视图的范畴,而更多的带入监督控制器和MVVM变异中。
MVP一般最常使用在企业级应用程序中,这样的程序中有必要对展现逻辑尽可能的重用。带有非常复杂的逻辑和大量用户交互的应用程序中,我们也许会发现MVC相对来说并不怎么满足需求,因为要解决这个问题可能意味着对多重控制器的重度依赖。在MVP中,所有这些复杂的逻辑能够被封装到一个展示器中,它可以显著的简化维护工作量。
由于MVP的视图是通过一个接口来被定义的,而这个接口在技术上唯一的要点只是系统和视图(展示器除外)之间接触,这一模式也允许开发者不需要等待设计师为应用程序制作出布局和图形,就可以开始编写展现逻辑。
根据其实现,MVP也许MVC更加容易进行自动的单元测试。为此常常被提及的理由是展示器可以被当做用户接口的完全模拟来使用,而因此它能够独立于其它组件接受单元测试。在我的经验中这取决于我们正在实现的MVP所使用的语言(超过一种取代Javascript来实现MVP的可选语言,同Javascript有着相当大的不同,比如说ASP.net)。
在一天的终点,我们对MVC可能会有的底层关注,可能将是保持对MVP的认可,因为它们之间的不同主要是在语义上的。一旦我们对清晰分离的关注被纳入到模型、视图和控制器(或者展示器)中,我们也许会获得大部分同样的好处,而不用去管我们所作出的选择的差异。
很少有,但是如果有任何架构性质的Javascript框架声称用其经典形式实现了MVC或者MVP模式的话,那是因为许多开发者并不认为MVC和MVP是相互冲突的(看到诸如ASP.net或者GWT这样的web框架,我们实际上更加可能会认为MVP被严格的实现了)。这是因为让我们的应用程序有一个附加的展示器/视图逻辑,同时也仍然当其是一种MVC的意味,是有可能的。
Backbone贡献者Irene Ros(位于波士顿的Bocoup)赞同这种想法,当她将视图分离到属于它们自己的单独组件中时,她需要某些东西来实际为她组装它们。这可以是一个控制器路由(比如Backbone.Router,在本书的后面会提到)或者一个对被获取数据做出响应的回调。
这就是说,一些开发者确实感觉Backbone.js更加适合于MVP的描述,相比于MVC。他们的观点是:
对此的回应会是视图也可以是一个View(如MVC),因为Backbone对于让它用于多用途有足够的弹性。MVC中的V和MVP中的P都能够通过Backbone.View来完成,因为它们能够达成两个目标:都用来渲染原子组件,还有将那个组件组装起来让其它视图来渲染。
我们也已经看到Backbone中控制器的责任Backbone.View和Backbone.Router都有分享,而在下面的示例中我们能够实际看到那方面实际上都是千真万确的。
在this.model.bind("change",..)一行中,我们的BackbonePhotoView使用了观察者模式来对视图的改变进行“订阅”。它也处理render()方法中的模板,但是并不像一些其它的实现,用户交互也在视图中处理(见events参数)。
var PhotoView = Backbone.View.extend({
//... is a list tag.
tagName: "li",
// Pass the contents of the photo template through a templating
// function, cache it for a single photo
template: _.template( $("#photo-template").html() ),
// The DOM events specific to an item.
events: {
"click img" : "toggleViewed"
},
// The PhotoView listens for changes to
// its model, re-rendering. Since tHere's
// a one-to-one correspondence between a
// **Photo** and a **PhotoView** in this
// app, we set a direct reference on the model for convenience.
initialize: function() {
this.model.on( "change", this.render, this );
this.model.on( "destroy", this.remove, this );
},
// Re-render the photo entry
render: function() {
$( this.el ).html( this.template(this.model.toJSON() ));
return this;
},
// Toggle the `"viewed"` state of the model.
toggleViewed: function() {
this.model.viewed();
}
});
另一种(完全不同的)看法是Backbone更加向我们前面考察过的Smalltalk-80MVC靠拢。
定期为Backbone写博客的Derick Bailey之前已经提到过,最终最好不要去强迫Backbone让其适应任何特定的设计模式。设计模式应该考虑指导可能如何被构建的灵活性,而在这一方面,Backbone既不适应MVC,也不适应MVP。相反,它从多个架构模式中借用了一些最好的经验而创造出了一个灵活的框架,并且工作得很好。
而理解这些这些概念源自哪里和为什么源自那里是值得去做的,因此我希望我对于MVC和MVP的阐述对此已经有所帮助。就叫它Backbone方法吧,MV*或带有的其它应用程序架构的意味。大多数结构性Javascript框架自主决定自身采用的经典模式,不管是有意还是无意为之的,最重要的是它们帮助了我们有组织,干净的来开发方便维护的应用程序。