从两个设计模式到前端MVC-洪宇

 

引言

本文将从策略模式和观察者模式两个设计模式讲起,接着过渡到一个经典的复合模式- MVC架构,进而介绍MVC在Web上的适应-Model2架构。之后,我们将视野扩展到前端MVC,看一看前端MVC经典的框架backbone,以及用backbone实现的案例todos和hello rocket,顺带了解一下很有前景的单网页应用-SPA。

策略模式

设想你要做一个鸭子模拟系统,里边有各种各样的鸭子,比如绿头鸭、红头鸭、甚至还有橡皮鸭。鸭子的叫声、捕食行为多种多样,可能有两种鸭子叫声相同,而另外两种鸭子捕食行为相同。我们希望能够少些一些代码,同时希望鸭子种类又容易扩展,该怎么办?

为了少写代码,我们自然地想到了继承。我们可以设计一个鸭子超类,在超类中实现捕食行为,然后让所有的鸭子继承这个行为,然后根据需要进行重载,达到代码复用的目的。可是,这样好吗?捕食行为多种多样,如果有两种鸭子的捕食行为一样但不同于超类中的,岂不是意味着我们需要些两份一样的代码?或者将继承关系弄得更复杂……

接口呢?如果鸭子会飞,我们就让它实现“飞行”这个接口,如果鸭子会叫,那就再实现一个“呱呱叫”接口。这样,我们就可以保证继承关系简单明了。不过复用代码就糟糕了,要知道,接口内是不能进行具体实现的……

怎么办?仔细观察一下我们会发现,如果我们实现了几种“飞行”的行为,实现了几种“捕食”的行为,再用“飞行”、“捕食”接口统一组织起来这些行为,那么描述一只鸭子就像是在搭积木一样,选定一种飞行行为,一种捕食行为,就建立出了一种鸭子。那些所有鸭子都一样的部分呢?放到鸭子超类中就可以了。这样一来,类的继承关系简单明确,重复的代码也不会有,而且加入鸭子也很容易-只需要选定相应行为,再继承一下就OK啦!

这就是策略模式了,它定义了“算法”簇,并分别封装起来,然后利用组合使得封装的模块之间可以相互替换,从而降低了代码之间的耦合性,达到更好扩展,更好维护的目的。在策略模式的最后,我们来看一下设计好的鸭子类图吧~

 

观察者模式

ASP.NET的按钮太丑了怎么办?设计一个漂亮的按钮取代它就好了!找几张漂亮的图片,然后设置好平时的样子、按下时的样子等等就可以啦。不过,既然是按钮,总得肩负起按钮的责任来吧。现在,假如我们是一个按钮,一起来想一想我们应当知道些什么……

自己被点了?嗯,系统先生肯定会告诉我们的,换句话说,我们肯定知道。

被点了之后要做的具体事情,比如打开一张图片?喂喂喂,我们是按钮,关心怎么打开图片干啥?再说了,也许换一个程序就变成播放音乐了呢。总不能把这些行为写到按钮类中去吧?

那如果客人说,如果你被点了我就要去播放音乐,怎么办?这个好说,让客人留下一个电话,如果系统告诉我,我被点了,那就给你打个电话就好了。

说了这么多,但这好像不是程序吧?其实变成程序也很简单,我们要求客人实现一个Listener接口,接口里边有个perform方法。从客人的角度来说,当按钮被点击之后想要做些什么,写在perform方法中即可。对按钮而言呢,它才不管客人具体要做什么呢,它只知道,如果自己被点了,调用所有Listener的perform方法就完成任务了。这样,客人的目的达到了,我们设计的按钮也可以复用到其它程序中啦!

这就是观察者模式,前边说的客人就是观察者,而按钮则被称为主题对象。它定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。最后,让我们看看观察者模式的一个形象图解吧~

 

复合模式-MVC

如果将以上两种模式加以组合,就诞生出了大名鼎鼎的MVC框架。MVC的全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写。如果把MVC想成一个夹心饼干,那么model和view就是夹心饼干外边的两片,而controller则是中间的奶油。下面,我们将分别介绍三者的作用。

模型:模型包含了程序所有的状态、数据以及程序逻辑。模型并不关心视图和控制器具体是什么,它就相当于观察者模式中的“主题”,提供了操纵和检索状态的接口,并发送状态改变通知给观察者,既视图或控制器。

视图:视图用来呈现模型,通常从模型中取得它需要的数据。视图包含与用户的交互,并将用户的输入交给控制器处理。视图和控制器实现了策略模式,视图是一个对象,可以被调整使用不同的策略,而控制器提供了策略。对于用户的输入,都会委托给控制器处理。

控制器:控制器负责与模型交互,传递用户的请求,例如播放音乐。当然,控制器也可能要求视图做出一些改变,比如将暂停按钮开启。控制器把控制逻辑从视图中分离,让视图和模型之间松耦合,更容易扩展。

下面是MVC结构的示意图,可以看到,MVC从设计模式中诞生,在广泛的使用中发展和壮大,下面就来介绍一下网页中的MVC。

 

Model 2

如果将MVC移植到浏览器/服务器模型,就变成了Model2。它的结构是这样的:

 

Model 2处理交互是这样的:1、你发出一个会被Servlet收到的HTTP请求,Servlet收到并解析数据。2、Servlet扮演控制器,向模型发送请求。3、控制器将控制权交给视图JSP。4、视图通过HTTP将页面返回浏览器。

Model 2将写前端页面的人员和后端代码的人员解耦,使得大家分工明确,责任清楚。

前端MVC

以上介绍的都是MVC在整个程序中的应用,甚至比较偏向后端。而在网页的前端,也有MVC框架可言。

前端的View是什么?我个人的理解是,与页面上元素直接相关的部分都属于View。包括html,CSS和一部分直接控制页面元素的JS。作为观察者模式中的观察者,它可以从Model中得到数据,并将其显示到页面上。而关于数据的变更与请求,则统统交给Controller处理。

那么Controller呢?作为Model和View的粘合剂,Controller将View方面的请求转发给合适的Model,在必要时也会去更新View。而Controller本身也可以作为Model的观察者,获取Model的变更。而作为Controller本身,就不应该有涉及到页面元素的代码了。

最后谈谈Model,与后端的沟通、AJAX请求以及对数据的处理都属于Model的工作。Model本身不知道谁是View,谁是Controller。它只提供一些方法供View和Controller调用,并且将变更通知给它的观察者View或Controller。显然,Model与页面元素之间也解耦了。

前端MVC各部分的职责如图所示:

 

在网上看到这样的一个例子,觉得比较清晰,有一点遗憾的是这个例子中,没有实现观察者模式,我们可以看到Model直接调用了View的某个方法。如果Model中有个数组,记录所有View观察者的名单,调用时直接遍历观察者数组,调用观察者的update方法,就更加满足MVC了。当然,对于这样一个短程序而言,将会更加复杂。

这个小程序会把你从下拉菜单"setAnimal"中选择的动物能做什么事回显到网页上。下面是没有应用任何设计模式的代码:

 

而将这样一个简单的小程序套上一个MVC的架子,就变成这样了:

 

 

 

前端MVC框架-backbone.js

从上面的例子可以看出,自己去写前端MVC还是很麻烦的。因而网上就有各种各样的框架给我们用,backbone.js是比较主流的框架之一,下面就来简单的介绍一下backbone.js。

在backbone.js中,你可以把数据作为Models,即通过Models你可以创建数据,进行数据验证,销毁或者保存到服务器上。当界面上的操作引起model中属性的变化时,model会触发change的事件。那些用来显示model状态的views会接受到model触发change的消息,进而发出对应的响应,并且重新渲染新的数据到界面。

backbone.js涉及到三个部分,即view、model、collection。model代表一个数据模型,collection是模型的一个集合,而view是用来处理页面以及简单的页面逻辑的。

Model的基本用法如下:

 

在这里,Man相当于一个类,initialize相当于构造函数。在构造函数中,我们绑定了监听,即当name属性被改变时,触发的响应事件。而defaults提供了name和age的默认值。另外,我们还可以在Man这个类中定义函数aboutMe。另一方面,man相当于Man类的一个对象,拥有set和get方法来设定或取得对象的属性值。

如果要向后端抓取或传送数据,则首先应当定义一个url,然后通过fetch和save方法来执行。其中,save方法会传送对象的属性值到服务端,而fetch方法的用法如下:

 

Collection相当于一个集合,它的基本用法如下,假定我们已经创建了一个类Book:

 

而从服务端抓取数据是这样写的:

 

另外,fetch操作之后,会调用reset方法,你还可以在这里绑定一个处理函数。

 

最后来看看View。下面的这个例子很好的描述了View的几个重要部分:创建、el属性和事件绑定。其中,el负责引用DOM元素,达到与界面沟通的目的。

 

你可能会问,MVC中的Controller去哪里呢?其实,view的其它部分更像是controller,而真正用来控制页面的都写在了view的render中。此外,backbone.js中还有router,在一定程度上也充当了controller的功能,不过需要通过调用Backbone.history.start()方法来初始化这个Router,具体用法如下:

 

另外,需要在页面上添加<a href="#actions">testActions</a>才行。

单网页应用SPA

单网页应用是指用户通过浏览器加载独立的HTML页面并且无需离开此导航页面,这也是其独特的优势所在。对用户操作来说,一旦加载和执行单个页面应用程序通常会有更多的响应,这就需要返回到后端Web服务器,而单页面应用为用户提供了更接近一个本地移动或桌面应用程序的体验。

由于不需要离开当前页面,因此单网页应用的用户体验很好。另外,由于单网页应用不需要提交所有数据,只需要部分刷新,对服务器的压力也能减轻很多。

然而,单网页应用维护起来可能相对困难,但是这个问题可以用框架来解决。另外,单网页应用还有一些历史遗留问题,特别是一些关于HTML5的问题。

Todos

Todos的model部分比较简单,我看到的教程上也有很好的注释,代码如下:

 

 

如果说,一个Todo对象描述一个任务的话,那么应当准备一个TodoList来描述一堆任务。显然,TodoList应当通过Collection来实现。

 

 

Comparator的作用是,定义一个排序的依据。而filter的作用是,选出列表中,制定项目为true的对象,这里指选出“已做完”的Todo,再加上without的配合,就变成了选出所有没做完的任务。

而View层包括两部分,TodoView和AppView。前者的作用是展示数据模型中的数据到界面,并对数据本身进行管理。而后者是对整体的一个控制,如所有数据的显示(调用TodoView),添加一个任务、统计多少完成任务等。由于教程上代码比较多,我就不贴在这里了,教程的地址请参见“参考资料”,里边注释写的也很清楚。

简而言之,View做了这么几件事情:1、绑定界面上的事件到View中的函数,例如"keypress .todo-input"事件与"updateOnEnter"处理函数绑定。2、部分View函数调用了Model的函数进行处理,相当于Controller,比如toggleDone函数。3、实现观察者模式,注册成为模型的观察者,接收模型的变化,例如语句this.model.bind('change', this.render)。4、界面的一些处理。

Hello Rocket

hello rocket主要就是做出了两个页面切换的效果,切换效果类似于PPT,利用了HTML5的相关技术。不过我在chrome下测试通过,但是在IE10下测试没通过,加载半天都没打开。下载源码,发现common_lib-aio.js是hello rocket依赖的一个库文件。而common_rocket-aio.js中,则包括了View、Collection、Model这几部分,此外还有pageview类,充当页面事件中心;router类,负责监听URL变化,并作转发,相当于controller;globalview类,控制全局的视图。View写的比较复杂,但主要还是监听、事件绑定这几部分,还有很多用于控制页面动画的代码。Model和Collection好像是个空壳?里边有那么几行代码还被注释掉了。

参考资料

1、《head first设计模式》

2、前端MVC:http://kb.cnblogs.com/page/41674/

3、百度图片搜索:

http://image.baidu.com/i?tn=baiduimage&ct=201326592&lm=-1&cl=2&nc=1&word=javascript%20mvc&ie=utf-8

4、backbone.js:

http://www.the5fire.com/backbone-tutorials-catalogue.html

http://blog.csdn.net/feng88724/article/details/7290433

5、单网页应用:http://www.csdn.net/article/2012-12-10/2812658-Single-Page-Applications

6、To Do:http://www.the5fire.com/backbone-tutorials-catalogue.html

7、Hello Rocket:https://github.com/MichaelHu/rocket_apps/tree/master/hellorocket

留下的问题和希望讲解的内容

1、  backbone的router和view中的render感觉看的不是很明白,希望多讲解一下。

2、  backbone为什么感觉少了个controller?或者说controller很不明显。

3、  大家接触前端MVC框架不多,多讲解一下设计时要注意的事项。

4、  Hello rocket比较晕,为什么Modal和Collection是个空壳?

原文地址:https://www.cnblogs.com/juicygroup/p/3356339.html