博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Web前端开发中的MCRV模式
阅读量:6084 次
发布时间:2019-06-20

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

 摘要

  针对前端开发中基于ajax的复杂页面开发所面临的代码规模大,难以组织和维护,代码复用性、扩展性和适应性差等问题,本文尝试以MVC思想为 基础,结合Web前端开发中“内容-结构-表现-行为”相分离的开发标准,提出一种将Web页面代码分为视图(View,页面静态部分,包括内容、结构、 表现)、模型(Model,负责数据缓存、数据校验与本地逻辑处理、发起ajax请求)、控制器(Controller,负责用户和系统事件响应、模型和 渲染器调度)、渲染器(Renderer,对视图的渲染,控制器与事件的绑定、数据搜集)的页面开发新模式,并基于此模式提出了一个开发框架原型。

  关键字

  MCRV设计模式,Javascript,MVC,Web开发标准

  1. Web前端开发面临的问题

  早期的Web页开发(Web前端开发)中,Web页面较为简单,大多数Web页面的功能仅限于用HTML和简单样式展示静态信息,或向服务器发 送数据,Web页面与用户的交互较少。随着Web的发展,DHTML、CSS、javascript等技术出现,Web页不再仅限于展示静态信息,动态、 交互成为Web页的主流功能之一。与此同时,Web页代码规模也变的较大,页面中的HTML、CSS、Javascript等代码往往混杂在一起,如何很 好的组织这些代码,使Web客户端程序具有很好的结构,易于阅读和维护,成为Web前端开发人员面临的一个难题。在实践中,业界提出了“内容 (Content) - 结构(Structure) - 表现(Presentation) - 行为(Behavior)”相分离的Web页开发标准。在这种标准中,一个Web页代码可以分为如下四个部分:

  内容:页面实际要传达的真正信息,包含数据、文档或者图片等。 结构:对内容的划分,使内容更加具有逻辑性,易用性;类似页面的标题、作者、章、节、段落和列表。 表现:用来描述内容外观,称之为“表现”,主要指CSS样式。 行为:行为就是对内容的交互及操作效果;行为控制主要通过javascript实现。

  内容 - 结构 - 表现 - 行为(CSPB)开发标准对Web页包含的的代码进行了分类,使代码各个部分得到很好的分离,使Web页初步具有了良好的结构。

  随后,Web进入了崭新的Web2.0时代,以Gmail为代表的Web2.0应用大量出现,并且取得很大的成功。这类Web页的典型特征是用 一个基于ajax技术的无刷新的页面完成大量复杂的功能。此类页面包括Web IM、Web Map、Gmail等应用,统称为one-page Web应用。同时,在企业级Web开发中,一个Web页同时完成多个复杂功能的情况也越来越多。在这些复杂Web页应用中, Javascript代码是此类富客户端应用程序的核心,负责与用户的复杂交互和页面展现。One-page Web页面Javascript代码规模往往非常庞大,逻辑复杂,动则千行以上,有时甚至需要一个团队来完成一个页面的开发。如何合理组织大量的 Javascript代码,使之具有良好的扩展性,能够适应需求的变化,使代码易于维护,是广大Web前端开发人员面临的一大挑战。

  纵观Web应用程序开发的历史,Web后端开发(PHP、J2EE、ASP.NET)与Web客户端开发的发展过程有一定的相似性,曾面临类似 的问题。最初,Web程序功能简单,代码较少时,所有的控制、逻辑、UI展现代码混杂在一起;随着Web发展,程序规模变大,按原有方式开发出的程序显然 不具有良好的结构,不利于大规模开发和后期维护,也不利于Web开发人员工作和角色的进一步细分,代码复用性差,瓶颈开始出现。Web后端程序设计者的解 决方法是进行代码分类,将控制代码与展现代码开始分离, 于是Web后端开发从所谓的Model1发展到Model2;同时,传统桌面程序设计中的MVC(模型-视图-控制器)设计模式被引入,负责程序数据与逻 辑计算的部分进一步分离出来,形成了Web后端开发中的MVC开发模式。MVC设计模式贯穿了软件工程分而治之的思想,有效解决了Web后端程序设计中的 代码组织和复用问题。采用同一设计模式(MVC)也使代码更容易被他人理解,保证代码可靠性;它使Web开发工作可以细分为业务逻辑开发和UI展现开发。 因此,MVC也利于团队开发。那么,MVC设计思想是否也能应用在前端开发中解决相关问题呢?既然问题具有许多相似性,笔者沿着相同的解决思路来试图解决 前端开发中的代码组织问题。

  2. MVC设计模式

  在提出本文提出的MCRV设计模式之前,有必要对其所基于的MVC设计模式进行阐述。

  MVC这个概念很早之前就被人提起[1],它代表一种设计思想。MVC是Model-View-Conroller的缩写,即模型 - 视图 - 控制器。这种开发模式将一个应用程序分为三个部分,模型(model)实现商业逻辑,提供数据;视图(view)负责向用户呈现界面和接受用户交互;控制 器(controller)则负责响应用户交互请求,对用户请求进行翻译,根据不同的请求调用模型,执行商业逻辑。控制器本质上是一个调度器 (dispatcher),通过其包含的各个方法(action)来执行具体的商业逻辑。MVC各个部件以最小的耦合性协同工作,从而使程序具有良好的可 扩展性和可维护性和复用性。

  MVC设计模式最开始被用于桌面程序设计,典型地用于相同的数据需要不同的用户交互界面的设计场景。在经典的MVC设计思想中,控制器负责响应 用户事件,根据事件类型和参数调用模型或改变视图。每个模型对应一个或者多个视图,当模型被控制器调用而得到改变时,模型向所有向其注册过的视图发送通 知,视图根据从模型的中得到的信息改变外观。这种设计模式可以用图1表示。可以看出经典MVC设计模式中Controller、Model 都可以改变视图。

图1 经典MVC

  基于MVC模式的Web开发(后端)一般可以用图2表示。在这种模式下,controller负责解析用户浏览器请求的URL,根据URL自动 调用controller中的不同的Action响应用户请求。Action调用model,然后将model返回的数据填充到view中,view被返 回给用户浏览器。Web服务端开发中典型的采用MVC模式的框架有CakePHP、Structs、Spring等等。

图2 Server端Web开发中的MVC

  相对于早期的经典MVC模式,Web开发中的MVC模式存在一些变化,因为对于一个桌面应用程序而言,可以很方便地将视图注册给模型,当模型数 据发生改变时,即时通知视图页面发生改变;而对于Web应用而言,即使将多个页面注册给一个模型,当模型发生变化时,模型无法主动发送消息给Web页面 (因为Web应用都是基于请求/响应模式的),只有当用户请求浏览该页面时,控制器才负责调用模型数据来更新Web页面。同时,Web开发中的MVC也比 经典MVC具有更小的耦合性,因为Model不再与View发生交互,因此程序的结构更加良好,使Web前端开发和业务逻辑开发相分离变的简单。

  对于具有复杂交互逻辑和丰富界面的程序来说,MVC已被实践证明是一种行之有效的设计和开发思想。采用MVC设计模式的系统具有较好的结构,较 低的耦合性,代码具有很好的维护性,能够适应复杂的业务逻辑和视图展现变化。它也非常适合团队开发,使开发人员分为不同的角色,专注于自己负责部分的开 发。

  3. MCRV设计模式

  综上所述,MVC是解决具有复杂交互界面、代码规模大的应用程序面临代码组织、复用问题的有效设计模式。基于此,本文尝试基于MVC设计思想解决前端开发中的类似问题。

  然而,在前端页面开发中不能直接套用MVC设计模式。因为,前端开发中的Web页面包含了HTML、CSS、Javascript等多个种类的 代码,相对Web后端来说,Web页面整体就是一个负责UI展现、用户本地交互、发送服务请求的大View,与经典MVC及Web后端开发MVC模式中的 View皆有不同。因此,需要具体问题具体分析。

  首先对Web页面中的HTML、CSS、Javascript等代码进一步分析,明确它们的具体功能分类。根据业界提出的Web开发标 准,Web页首先被分为了"内容-结构-表现-行为"几个部分。内容、结构、表现是页面的静态部分,主要负责UI的展现,用户操作指令(键盘、鼠标)的接 受,因此,内容、结构、表现属于视图 (View)的范畴。行为主要就是Javascript代码,负责对用户操作指令的响应。在复杂ajax应用中, Javascript代码的功能一般包括:响应用户指令,执行数据验证/处理、执行客户端交互逻辑,向服务器发送ajax请求,接受并处理服务器返回的数 据,根据数据改变UI(向页面结构填充内容数据、变换样式等)。基于模型-视图-控制器分离的思想,Javascript代码中的数据验证/处理、本地业 务逻辑计算、向服务器发请求获取数据的功能,对应模型(Model)的功能;响应(翻译)用户操作指令,根据指令执行业务逻辑处理的功能,属于控制器 (Controller)的功能;而接受处理过的数据,根据数据修改页面的内容/结构/样式的功能既不属于控制器的功范畴,也不属于模型的功能范畴,这部 分代码因为根据数据对View进行呈现(render),可以命名为Renderer(渲染器或呈现器)。

  根据前述分析,本文提出了MCRV设计模式,如图3所示。阐述如下:

图3: MCRV开发模式

  M(Model):模型,完成数据验证、数据处理,执行客户端业务逻辑计算,或向服务器发起ajax请求调用服务端逻辑、接受返回的数据,将处 理后的数据返回控制器。 C(Controller):控制器,控制器响应View上的事件,根据事件调度执行模型的业务逻辑,从业务逻辑获取返回数据,调度相应的渲染器 (Render)来完成界面展现。在这个过程中控制器会有数据的传递:控制器调用模型中的逻辑时会传送Renderer搜集的数据(form表单各域的 name/value、其他控制参数),模型执行逻辑后返回作为执行结果的数据给控制器,控制器根据数据来调用渲染器(renderer)来完成界面呈现 (rendering),呈现(rendering)就是修改页面结构、内容和样式的过程。数据传递过程可以用图4表示。 R(Renderer):渲染器(呈现器),渲染器被控制器调用,接受从控制器传递的数据,完成对界面的具体渲染。渲染器也负责控件(widget)的初 始化,及建立Controller与具体事件的对应关系,事件发生时负责搜集View上的数据传送到Controller。 V(View):视图,视图是用户最终看到的整个Web界面,由结构、内容、样式(表现)等静态内容共同构成;View由Renderer进行初始化渲染 和修改。

 

图4:数据传递过程

  可以看到在MCRV开发模式中,Controller处于控制中心的位置,Model完成具体的商业逻辑计算以及向后端发起ajax请求返回数 据的功能。Controller与Model、Renderer之间的交互本质上是数据交互的过程,它们之间存在着一个数据流,如图4所示。因此,制定 Controller、Model、Renderer之间的交互接口时,数据格式定义很重要。

  4. 基于MCRV设计模式的Demo

  下面是一个用MCRV模式来开发的Demo页面,页面的功能是用表格展示和修改用户信息。Javascript使用了jQuery库。页面界面如图5所示。页面代码在程序清单1中

图5:基于MCRV设计模式的用户管理Demo

  程序清单1

<!
DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"
>
<
html
>
<
head
>
<
meta
http-equiv
="content-type"
content
="text/html;charset=utf-8"
>
<
meta
http-equiv
="X-UA-Compatible"
content
="IE=7"
/>
<
title
>
MCRV 设计模式 Demo
</
title
>
<
style
type
="text/css"
>
/*
*******表现********
*/
table
{
width
:
100%
;
border-collapse
:
collapse
;
}
td
{
border
:
1px solid black
;
padding
:
2px
;
}
#container
{
width
:
800px
;
margin
:
0px auto
;
}
#tbUsers
{
margin
:
20px auto
;
}
#tbUsers th
{
background-color
:
navy
;
color
:
white
;
text-align
:
center
;
vertical-align
:
middle
;
border
:
1px solid navy
}
#tbUsers td
{
text-align
:
center
;
}
.editCaption
{
width
:
100px
;
text-align
:
right
;
}
.buttonMargin
{
margin
:
0px 20px
;
}
.buttonContainer
{
text-align
:
center
;
vertical-align
:
middle
;
height
:
50px
}
</
style
>
<
script
src
="./jquery-1.3.2.min.js"
type
="text/javascript"
charset
="utf-8"
></
script
>
</
head
>
<
body
>
<!--
-----结构-------
-->
<
div
id
="container"
>
<
table
id
="tbUsers"
>
<
thead
><
th
>
id
</
th
><
th
>
姓名
</
th
><
th
>
年龄
</
th
><
th
>
修改
</
th
></
thead
>
<
tbody
/>
</
table
>
<
div
style
="display: none;"
id
="dvEditPanel"
>
<
form
id
="frmModify"
name
="frmModify"
>
<
table
>
<
tr
>
<
td
class
="editCaption"
>
id:
</
td
>
<
td
><
span
id
="spID"
></
span
></
td
>
</
tr
>
<
tr
>
<
td
class
="editCaption"
>
姓名:
</
td
>
<
td
><
input
type
="text"
size
="20"
id
="txtName"
/></
td
>
</
tr
>
<
tr
>
<
td
class
="editCaption"
>
年龄:
</
td
>
<
td
><
input
type
="text"
size
="20"
id
="txtAge"
/></
td
>
</
tr
>
<
tr
>
<
td
colspan
="2"
class
="buttonContainer"
>
<
button
id
="btnSubmitModify"
class
="buttonMargin"
type
="button"
>
提交
</
button
>
<
button
id
="btnCancelModify"
class
="buttonMargin"
type
="button"
>
取消
</
button
>
</
td
>
</
tr
>
</
table
>
</
form
>
</
div
>
</
div
>
<
script
>
/*
**************************行为*******************************
*/
var
UserManagerMCR;
$(
function
()
{
UserManagerMCR
=
new
MCR(UserController,UserModel,UserRenderer);
});
/*
* MCR 三元组
*/
function
MCR(Controller,Model,Renderer)
{
this
.controller
=
new
Controller();
this
.model
=
new
Model();
this
.renderer
=
new
Renderer();
this
.controller.model
=
this
.model;
this
.controller.renderer
=
this
.renderer;
this
.model.controller
=
this
.controller;
this
.renderer.controller
=
this
.controller;
if
(
typeof
this
.model.init
==
"
function
"
)
{
this
.model.init();
}
if
(
typeof
this
.renderer.init
==
"
function
"
)
{
this
.renderer.init();
}
if
(
typeof
this
.controller.init
==
"
function
"
)
{
this
.controller.init();
}
}
/*
* 控制器
*/
function
UserController()
{
this
.init
=
function
()
{
this
.initUserList();
}
this
.initUserList
=
function
()
{
var
list
=
this
.model.getUserList();
this
.renderer.renderUserList(list);
}
this
.beginModify
=
function
(data)
{
var
user
=
this
.model.getUserByID(data.id);
this
.renderer.showModifyUI(user);
}
//
提交修改
this
.submitModify
=
function
(user)
{
var
result
=
this
.model.modifyUser(user);
if
(result.success)
{
var
list
=
this
.model.getUserList();
this
.renderer.renderUIWhenSubmitModifySuccess(list);
}
else
{
alert(result.msg);
}
}
//
取消修改
this
.cancelModify
=
function
()
{
this
.renderer.hideModifyUI();
}
}
/*
* 模型
*/
function
UserModel()
{
//
模拟的数据,实际应用中经常从服务器获取
this
.init
=
function
()
{
this
.data
=
[
{id:
0
,name:
"
John
"
,age:
22
},
{id:
1
,name:
"
Tom
"
,age:
30
},
{id:
2
,name:
"
Tony
"
,age:
25
}
];
}
//
获得用户数据列表
this
.getUserList
=
function
()
{
//
todo ,可能ajax从后端返回
return
this
.data;
}
//
获得用户数据
this
.getUserByID
=
function
(id)
{
var
ix;
$.each(
this
.data,
function
(i,item){
if
(item[
"
id
"
]
==
id ) { ix
=
i;
return
false
;}});
return
this
.data[ix];
}
//
修改用户数据
this
.modifyUser
=
function
(user)
{
var
result
=
{success:
true
,msg:
"
修改成功
"
};
//
todo,验证参数user
//
todo,修改用户数据
$.each(
this
.data,
function
(i,item)
{
if
(item[
"
id
"
]
==
user[
"
id
"
])
{
item[
"
name
"
]
=
user[
"
name
"
];
item[
"
age
"
]
=
user[
"
age
"
]
return
false
;
}
});
return
result;
}
}
/*
* 渲染器
*/
function
UserRenderer()
{
this
.init
=
function
()
{
var
me
=
this
;
$(
"
#btnSubmitModify
"
).click(
function
()
{
var
user
=
{id:$(
"
#spID
"
).text(),name:$(
"
#txtName
"
).val(),age:$(
"
#txtAge
"
).val()};
me.controller.submitModify(user);
});
$(
"
#btnCancelModify
"
).click(
function
()
{
me.controller.cancelModify();
});
$(
"
#tbUsers .modify
"
).live(
"
click
"
,
function
()
{
var
id
=
$(
this
).attr(
"
uid
"
);
me.controller.beginModify({
"
id
"
:id});
});
}
this
.renderUserList
=
function
(list)
{
var
htm
=
[];
for
(
var
ix
=
0
;ix
<
list.length;ix
++
)
{
htm.push(
"
<tr><td>
"
+
list[ix][
"
id
"
]
+
"
</td>
"
+
"
<td>
"
+
list[ix][
"
name
"
]
+
"
<td>
"
+
list[ix][
"
age
"
]
+
"
</td>
"
+
"
<td>
"
+
"
<a class='modify' href='javascript:void(0)' uid='
"
+
list[ix][
"
id
"
]
+
"
'>修改</a></td>
"
+
"
</tr>
"
);
}
$(
"
#tbUsers
"
).children(
"
tbody
"
).html(htm.join(
""
));
}
this
.showModifyUI
=
function
(user)
{
$(
"
#dvEditPanel
"
).show();
$(
"
#spID
"
).text(user[
"
id
"
]);
$(
"
#txtName
"
).val(user[
"
name
"
]);
$(
"
#txtAge
"
).val(user[
"
age
"
]);
}
this
.hideModifyUI
=
function
()
{
document.frmModify.reset();
$(
"
#dvEditPanel
"
).hide();
}
this
.renderUIWhenSubmitModifySuccess
=
function
(list)
{
this
.hideModifyUI();
this
.renderUserList(list);
}
}
</
script
>
</
body
>
</
html
>

  5. 基于MCRV模式的开发框架原型

  5.1 基本设计思想

  框架是在一个特定的问题领域内,应用程序的部分设计与实现[2]。框架与设计模式通常是精密结合的。框架规定了应用的体系结构,使基于特定设计 模式的开发能够复用公共代码;反之,框架强调设计复用,框架设计也基本上使用了设计模式,掌握了框架的设计模式可以快速掌握基于框架的应用程序开发。因 此,使用MCRV设计模式进行前端开发时最好有一个与之配合的Javascript框架。

  基本设计考虑:

  Model、Controller、Renderer组成一个三元组MCR,一个MCR三元组中Controller对象、Model对象和Renderer对象是唯一的。

  Model、Controller、Renderer可以初始化和销毁。

  Controller、Model、View在需要时可以透明地引用其他对象。

  数据与逻辑分离,支持数据本地存储。

  一个Web页面可以有多个MCR组,即可以有多个Controller、Model、Renderer三元组分别完成不同的控制、逻辑、展现。这为一个页面逻辑和交互非常复杂时,拆分为多个模块,由多人开发提供了支持。

  5.2 基本对象及其关系

  下面是一个基于上述考虑的基于MCRV设计模式的Javascript框架原型(以下简称MCRV框架)。

  框架的基本对象:MCR、Model、Controller、Renderer、Cache。如图6所示。

图6 基于MCRV模式的框架原型

  MCR:控制器(Controller)对象、模型对象(Model)和渲染器对象(Renderer)的复合对象。

  - Dispose()方法:执行MCR销毁操作,避免Javascript内存泄露;在页面unload时自动调用;这个方法自动调用Model、Controller和Renderer的dispose()方法。
  - model属性:包含的模型。
  - controller属性:包含的控制器。
  - renderer属性:包含的渲染器。

  Model:模型对象。

  - init()方法:执行模型初始化。
  - dispose()方法:执行对象销毁,释放资源。
  - cahce属性:数据的缓存。

  Controller:控制器对象

  - init()方法:执行控制器初始化。
  - dispose()方法:执行对象销毁,释放资源。
  - model属性:调度的模型。
  - renderer属性:控制的渲染器。

  Renderer:渲染器对象

  - init()方法:执行渲染器初始化。
  - dispose()方法:执行对象销毁,释放资源。
  - controller属性:对controller的引用。

  Cache:基于key-value的缓存对象

  - get()方法:基于键值获取缓存数据。
  - set()方法:设置缓存数据。
  - remove()方法:删除对应某个键值的缓存。
  - size():缓存数量。

  5.3 MCRV框架与其他web其他部分的关系

  基于MCRV模式的开发框架与Web页面开发其他组成部分的关系可以用图7表示。MCRV框架搭建起了js应用程序整体的结构,提供了应用程序 上下文环境。MCRV框架可以与js组件库、css基础样式库和js业务逻辑组件一起作为应用程序构建的基础,并且它们之间没有依赖关系。尽管应用程序可 以在js基础库(如jQuery)上开发,但是MCRV框架不依赖这些库。

图7 基于MCRV模式的开发框架与其他Web页其他部分的关系

  6. 结论

  实践表明,MCRV设计模式能够有效解决复杂ajax开发中面临的问题,可以使Web页面代码结构良好,降低Javascript代码的耦合性,提高复用性、适应性和灵活性,使Web页更加易于维护和重构。

  参考文献

  [1]. Trygve Reenskaug Taskon.Working with objects in the user interfaces.

  [2]. J. Van Gurp, J. Bosch.Design, Implementation and Evolution of Object Oriented Frameworks Concepts and Guidelines.pdf.

 

  作者: izujian  来源:

 

转载地址:http://rbkwa.baihongyu.com/

你可能感兴趣的文章
CSS3让长单词与URL地址自动换行——word-wrap属性
查看>>
CodeForces 580B Kefa and Company
查看>>
开发规范浅谈
查看>>
Spark Streaming揭秘 Day29 深入理解Spark2.x中的Structured Streaming
查看>>
鼠标增强软件StrokeIt使用方法
查看>>
本地连接linux虚拟机的方法
查看>>
某公司面试java试题之【二】,看看吧,说不定就是你将要做的题
查看>>
BABOK - 企业分析(Enterprise Analysis)概要
查看>>
Linux 配置vnc,开启linux远程桌面
查看>>
Hadoop安装测试简单记录
查看>>
CentOS6.4关闭触控板
查看>>
React Native 极光推送填坑(ios)
查看>>
Terratest:一个用于自动化基础设施测试的开源Go库
查看>>
修改Windows远程终端默认端口,让服务器更安全
查看>>
扩展器必须,SAS 2.0未必(SAS挺进中端存储系统之三)
查看>>
Eclipse遇到Initializing Java Tooling解决办法
查看>>
while((ch = getchar()) != '\n')
查看>>
好程序员web前端分享JS检查浏览器类型和版本
查看>>
Oracle DG 逻辑Standby数据同步性能优化
查看>>
exchange 2010 队列删除
查看>>