Truly
写精彩代码 品暇逸人生

作者:Truly
日期:2007.7.24

前言

上篇文章我们介绍了在JavaScript中使用面向对象的方法,本文我们则讨论软件工程领域的另一个革新--设计模式在JavaScript中的应用。
模式的概念诞生于20世纪70年代,最初用于描述建筑领域的一些特定问题的解决方案。后来这一方案也被应用到软件开发这一领域。在我们使用Java或C++构建大型应用程序的时候,我们几乎无法离开设计模式,并且,在这些领域有着丰富的设计模式文化。微软最近也极大的推动了.Net框架下设计模式的应用,时至今日,您可能已经在上述领域积聚了相当丰富的理论和实践经验,然而,本文要讨论的是如何在JavaScript程序设计中使用模式。同时,为了不使这篇文章满是学究味,我决定用一个项目作为示例进行讨论。

目录

开始
介绍
MVC模式

开始

正如我上篇文章《在JavaScript中使用面向对象》一文中讲的,这一语言的特点就是简单宽松。所以对于JavaScript,即使您不使用面向对象、设计模式,一样可以开发出可用的程序来。但是如果采用了设计模式,无疑您的程序将具有更好的健壮性,可维护性以及易读性。所以,作为能工巧匠的您,也一定不会放过另程序蓬荜生辉的机会。

介绍

这里首先要介绍一下作为演示的项目,这是一个内容管理系统,用来管理公司的新闻内容,通过后台文章在线编辑,自动发布到公司的几个网站上面。为了获取更好的性能和简便的发布方式,我们最终决定使用静态HTML作为文章呈现载体。讲到这里,也许您已经想到动态生成HTML的方案,但是这样会有几个重要弊端,比如网站改版则需要重新生成所有的HTML页面,而且要占据相当大的磁盘空间,同时生成模板的修改也是对美工的一大考验,最终整个过程变得非常复杂,那么有什么好的解决方案吗,答案是肯定的。现在该向大家介绍今天的主角了:MVC(模型视图控制器)模式。这一设计模式被我们反复使用了多年,但是,今天我们还是再一次回顾一下MVC的知识:

MVC模式

MVC
,英文全名是Model/View/Controller,这一模式常被用来构建用户界面,最典型的作用就是将程序与用户交互的部分进行分离,可能覆盖应用的所有层或者跨越多个层,比如经典书籍《设计模式--可复用面向对象软件的基础》一书中对文字处理程序界面的处理。MVC中最重要的一点是视图不应该与模型相互通信,例如当用户操纵页面上的一个按钮时,不应该直接与数据模型进行交互,而应该将这一交互过程通过控制器来完成。整个过程如下:

MVC模式图

如果用户想从模型中获取某个数据并显示在页面上,那么这一请求必须通过控制器来完成,获取数据并更新视图。这样的好处就在于视图和模型间出于松散耦合,他们之间没有直接联系,不需要了解对方的内部实现。MVC的好处在Web应用中表现更为明显,对从事Web程序开发的人而言,分离视图和模型几乎是非常自然的。MVC的应用司空可见,例如ASP.NET 1.x时代的code-behind和2.0时代的code-beside等等,我们总是把视图写在aspx页面,而把代码写到cs文件中。当然,也有人将MVC定义的更为苛刻,他们将所有产生的HTML的代码和相关代码都归结为视图。但是无论从广义还是狭义上,在Web应用程序的开发中,MVC都得以普遍应用。

好了,现在回到我们的项目上来讲,同样是出于性能的考虑,我们决定将数据和图片保存在与前端Web Server不同的一台服务器上,以分散前端服务器的压力,但是由于XML跨域的安全限制,使得我们最终放弃了XML作为数据载体。那么作为XML最好的替代品正是JSON,恐怕找不到比它更为合适的了,而且使用JSON带来的好处远远超过XML所能给项目带来的好处。如果您长期从事Web 2.0技术研究,那么这应该是您非常熟悉的一种技术了。如果您还不够了解JSON,那么您可以通过阅读我的《深入浅出JSON》一文对其进行了解。

基本技术确定下来后,下面最终的解决方案架构图:

 

至此整个系统已经一目了然了,接下来就是实施过程,对于内容管理系统,除了后台的文章编辑和分类管理等,对于每个网站的前台显示我们需要2大功能,一个是新闻列表,一个是新闻查看,这里我们需要编写几个js文件,并且分别对应不同的HTML页面,所有相关的文件分别如下:

控制器
base.js  //存放了一些服务器配置列表配置等信息
loadlist.js
loaddata.js

视图
list.html
detail.html

数据模型
list_xx.js // 文章列表的数据
xx.js  //文章详细内容的详细数据

首先我们定义了文章的数据模型:

                        

与数据库对应,这个模型包含了文章的几乎所有需要用于显示的详细数据,并最终映射为JSON数据类型,封装生成到文章对应的js数据文件19.js,参见下面代码:

var Article={
"ID":19,
"CatalogID":10,
"CatalogID":1,
"Title":"Hello world!",
"Content":"Welcome to learn the JavaScript patterns.",
"Author":"Truly",
"IsEssential":false,
"CreateDate":new Date('Wed, 25 Jul 2007 09:59:00 GMT+8'),
"PublishDate":new Date('Wed, 25 Jul 2007 09:59:21 GMT+8'),
"UpdateDate":new Date('Wed, 25 Jul 2007 09:59:21 GMT+8'),
"ExpireDate":new Date('Mon, 01 Jan 2046 00:00:00 GMT+8'),
"PageIndex":1,
"PageCount":1
"Status":1,
}

然后是为列表设计的两个数据模型:ArticleListArticleListInfo,见下图:

                                                    

这两个数据模型都是为文章列表服务的,所以我们将其合并生成到同一个js文件中,比如这个列表是罗列CatalogID=10的所有文章,list_22.js:

var ArticleList=[
                
{"ID":"866","WebSiteID":"10","CatalogID":"42","IsEssential":false,"Author":"Truly","Title":"Title1","PublishDate":new Date('Thu, 19 Jul 2007 11:18:00 GMT+8')}
                ,
{"ID":"864","WebSiteID":"10","CatalogID":"42","IsEssential":false,"Author":"Truly","Title":"Title2","PublishDate":new Date('Thu, 19 Jul 2007 11:12:00 GMT+8')}
                ,
{"ID":"836","WebSiteID":"10","CatalogID":"42","IsEssential":false,"Author":"Truly","Title":"Title3","PublishDate":new Date('Wed, 18 Jul 2007 10:35:00 GMT+8')}
                ,
{"ID":"817","WebSiteID":"10","CatalogID":"42","IsEssential":false,"Author":"Truly","Title":"Title4","PublishDate":new Date('Mon, 16 Jul 2007 17:17:00 GMT+8')}
                    ]
var ArticleListInfo = {"ArticleListID":44,"ArticleListType":2,"ArticleListNum":20,"PageIndex":1,"PageSize":20,"PageCount":1}

为了演示需要,我为代码适当增加了空格换行等控制字符,正常情况下我们不会在生成js的时候增加换行递进等控制符。

根据JavaScript的特性我们将一些配置信息定义为全局变量存放在base.js文件中,方便以后的部署维护工作。
base.js

/************************************************************************
* Author: Truly
* Email:  zhuleipro#hotmail.com
* WEB:    http://truly.cnblogs.com
* Date:   2007.7.11
* Note:   This file including some Chinese, 
*         Please save as utf-8 encoding.
***********************************************************************
*/

/********************* 配置区开始 ********************/

// js数据服务器
var GLOBAL_DATAHOST = 'http://data.domain.com/list/';

// 新闻完整列表ID常量
var OFFICIALNEWS = 33;
var SYSTEMANNOUNCEMENT = 34;
var ACTIVITYNEWS = 35;
var OTHERNEWS = 36;

.

最后,我们编写控制器代码:

/************************************************************
* Author: Truly
* Email:  zhuleipro#hotmail.com
* WEB:    http://truly.cnblogs.com
* Date:   2007.7.11
*
************************************************************
*/

// 显示列表
function RunShowMethod()
{
    
var iPageCount = ArticleListInfo.PageCount;
    
var iPageSize = ArticleListInfo.PageSize;
    
var iPageIndex = ArticleListInfo.PageIndex;
    
    
var strNewIcon="";    
    
var count = ArticleList.length;
    
var arr = new Array(count);

    
for (var i=0;i<count; i++)
    {    
        
if(typeof ArticleList[i].ATPublishDate == "string")
            dt 
= new Date(Date.parse(ArticleList[i].ATPublishDate));
        
else
            dt 
= Date.parse(ArticleList[i].ATPublishDate);
        
        
// 列表模版      
        arr[i] = "<tr>\
                     <td>&nbsp;</td>\
                     <td><img src=\
"images/icon.jpg\" width=\"12\" height=\"15\"></td>\
                     <td><a href='
" + strCommunionDetailPage + "?id=" + ArticleList[i].ATID + "&catID=" + ArticleList[i].ATCategoryID + "' target='_blank' class='S9pt'>" + ArticleList[i].ATTitle + "</a></td>\
                     <td>
"  + dt.toString() +  "</td>\
                     <td>&nbsp;</td>\
                   </tr>
";
        
// 分隔行模版
        if(i != count -1 )
            arr[i]
+="<tr><td colspan=\"5\" nowrap align=\"center\"><hr></td></tr>";
    }
    
    
// 显示列表内容
    
    $(
"lbContent").innerHTML = "<table width=\"100%\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\">\
                                    <tr><td height=\
"5\"></td></tr>"
                                         
+ arr.join(""+ " \
                                    <tr><td height=\
"10\"></td></tr>\
                                </table>
";    
    
// 显示分页导航条
    $('pager').innerHTML = getPagerHTML(getParm("page"), iPageCount);
 }
// 获取分页HTML代码
function getPagerHTML(pageIndex, pageCount, group)
{
    
// 如果只有1页,不再生成页码
    if(pageCount == 1return "";    
    
if(!pageIndex) pageIndex = 1;    
    strPager 
= ""
    
if(!group ) group = 10;
    
    
var m = Math.floor(group / 2+ 1;
    
var start = pageIndex - m +1
    
if(start < 1) start = 1;
    
var end = group+start-1;
    
if(end > pageCount) end = pageCount;
    
if(end - start < group)
    {
        start 
= end - group + 1;
        
if(start < 1) start = 1;
    }
    
    
for(var i=start;i<=end;i++)
    {
        
if(i == pageIndex)
            strPager 
+= "&nbsp;<span class=\"STYLE13\" ><font color=\"#d20d67\"><b>" + i + "</b></font></span>";
        
else
            strPager 
+= "&nbsp;<a href='"+PageName+"?page=" + i + "' class='Blue9pt'>" + i + "</a>";
    }
    
    
if(pageIndex > 1)
        strPager 
= "<a href='"+PageName+"?id=" + listID + "&page=" + (pageIndex -1+ "' class='Blue9pt'>上一页</a> " + strPager;
    
else
        strPager 
= "<font class='Blue9pt'>上一页</font> " + strPager;
        
    
if(pageIndex < pageCount)
        strPager 
+= "&nbsp;&nbsp;<a href='"+PageName+"?id=" + listID + "&page=" + (parseInt(pageIndex)+1+ "' class='Blue9pt'>下一页</a>";
    
else
        strPager 
+= "&nbsp;&nbsp;<font class='Blue9pt'>下一页</font>";
        
    
return strPager;
}

这些控制器依赖HTML页面上的一些容器ID,例如lbTitle,lbContent,lbCreateDate等,最后在还需要HTML适当位置增加一些加载语句,或者在控制器中处理文档的onload事件加载。例如:

    var tet = document.createElement('<SCRIPT>');
    tet.src 
= jsToLoaded[LoadedCount] + "?tmp=" + Math.random();
    document.body.appendChild(tet);

同时结合JavaScript文件加载,我们在数据js中还增加了主动更新语句:

if(typeof(RunShowMethod) == "function") RunShowMethod();

这样当页面加载结束的时候,可以主动去填充到HTML中。

我们后面讲Observe模式的时候,再详细讨论这个,还有部分函数我没有放出源码,大家可以自己进行完善,这个系统是支持文章分页的。

通过上面的具体分析,我们可以看出,在使用了MVC模式后,整个系统变得简单明了。通常,情况下数据模型不需要进行任何调整,如果出现网站改版需要,只需调整对应的list.html和detail.htm2个页面就可令这个网站全面更新,同时数据的独立存放,也极大的减少了重复HTML代码造成的空间浪费。

至此,我们的第一个模式MVC的讨论就结束了。这个方案为我们很好的演示了MVC在实际项目中的应用,但是需要注意的是它的一些缺陷。


接下来是Observe、Facade、Adapter、Singleton等模式在JavaScript中的应用,敬请期待。

to be continued....

posted on 2007-07-24 23:42  Truly  阅读(4261)  评论(7编辑  收藏  举报