posts - 5, comments - 16, trackbacks - 0, articles - 0

2006年9月3日

收藏一篇DLEE老大的文章,里面的话句句说到了我的心坎里。

为什么我说Struts/WebWork会受到Ajax的威胁呢?有的人可能觉得大家相安无事不是很好,你是不是有神经病故意挑起人民内部矛盾?问题是他们之间确实存在着一些深层的内在矛盾和冲突,这些矛盾才是目前Struts和 WebWork都只能在非常有限的程度上支持Ajax的原因。所以,问题是架构性的,并不是小型的修补或者更好的编程技巧可以彻底解决的。

传统的服务器端MVC架构设计(也就是Model2),存在着一个基本的假设就是Web应用的工作流是由一系列的页面切换构成的。这种架构中的一个View,从语义上来讲只能代表一个完整的HTML页面。整个Web应用的表现层,被划分成为非常多的页面的组合。

而Ajax开发者眼里,Web应用的工作流并不是这样构成的。Ajax开发者看待Web应用的角度与传统开发者相比差别非常大。在一个Ajax应用中,只有相对很少的页面。每个页面,包括页面引用CSS样式、JS脚本,都是一个更小型的Ajax应用。甚至一些功能简单的Ajax应用,本身仅仅由一个单一的页面构成。例如一个简单的RSS阅读器,还有IBM笔记本上那个获得天气预报的桌面。
按照Ajax in Action,Ajax应用可以分成3种类型:

  1. 以内容为中心的应用,服务器返回的是一段HTML内容。
  2. 以脚本为中心的应用,服务器返回的是一段JS脚本。
  3. 以数据为中心的应用,服务器返回的是一段数据,可以是XML格式、JSON格式或者其他文本格式。
服务器返回给Ajax应用的3种类型的网络流量(不称为数据是与上面第3种Ajax应用相区别),任何一种都不能被简单地视作传统MVC架构中的View,因为他们各自所代表的语义与传统MVC架构中的View的语义是完全不同的。所以可以看出,除了初次交付给浏览器一个完整的Ajax应用之外,传统的MVC架构对于Ajax应用的支持是非常有限的。其实为了给客户端提供上面3类网络流量,一个Servlet已经足够了。DWR、JSON-RPC、Buffalo在服务器端也就是由Servlet实现的,不要求服务器端一定要安装某种MVC框架。
上面3类应用,前面的两类,客户端JS代码比较简单,表现逻辑仅有一部分位于客户端,大部分仍然位于服务器端,因此传统的服务器端MVC架构仍然是非常有价值的。但是大家注意第3类Ajax应用,实际上它已经将绝大部分甚至可以将全部的表现逻辑都转移到客户端来执行,这个时候服务器端传统的Web表现层实际上被架空了(皮之不存,毛将焉附?)。而对于Ajax应用来说,虽然近期可能还是以第1类Ajax应用为主(例如,所谓的AHAH技术),但是最有生命力和发展前景的还是第3类Ajax应用。

自从1999年M$推出IE5.0支持XMLHTTP,可以不刷新页面以异步方式从服务器获取数据之后,Web开发的领域就埋下了一颗定时炸弹(6年以后,一个新词Ajax的出现引爆了这颗炸弹)。Model2最初的设计应该发生在这件大事(现在应该承认,M$做了一件天大的好事)发生之前,其设计师不可能想到异步请求的价值。按照Model2的设计思想直接产生了Struts。但是后来的WebWork在最初设计阶段仍然与这个技术失之交臂,这是相当可惜的一件事情。WebWork其实最初设计的时候就可以走的更远,但是他们只想超越Struts,做一个更好的Model2 MVC开发框架。现在他们再想赶上这班列车已经有点晚了。如果基础的服务器端MVC架构的价值是可疑的,那么其他围绕这个架构所开发的基础架构的价值也同样是可疑的。

所以在现在这个时刻,重新正本清源地思考Model2最初的设计,它带来的Web开发的巨大进步,以及它所存在的不足,是一个非常现实的问题。

posted @ 2006-09-08 22:16 BennyBao 阅读(273) | 评论 (0)编辑 收藏

     摘要: 在网上看到了有些同志提到了为Ajax的XMLHttpRequest提供一个对象池,也读了他们给出的实现代码。感觉不是特别理想,于是模仿apache的commons中的ObjectPool的思路写了一个简单的JavaScript版。望指教: function ...  阅读全文

posted @ 2006-09-08 17:47 BennyBao 阅读(2467) | 评论 (3)编辑 收藏

在JavaScript可以使用try...catch来进行异常处理。例如:
try   {
    foo.bar();
}
  catch  (e) 
{
    alert(e.name 
+   " "   +
 e.message);
}

目前我们可能得到的系统异常主要包含以下6种:
  • EvalError: raised when an error occurs executing code in eval()
  • RangeError: raised when a numeric variable or parameter is outside of its valid range
  • ReferenceError: raised when de-referencing an invalid reference
  • SyntaxError: raised when a syntax error occurs while parsing code in eval()
  • TypeError: raised when a variable or parameter is not a valid type
  • URIError: raised when encodeURI() or decodeURI() are passed invalid parameters

上面的六种异常对象都继承自Error对象。他们都支持以下两种构造方法:

new  Error();
new  Error( " 异常信息 " );

手工抛出异常的方法如下:

try   {
    
throw   new  Error( " Whoops! "
);
}
  catch  (e)  {
    alert(e.name 
+   " "   +
 e.message);
}

如要判断异常信息的类型,可在catch中进行判断:

try   {
    foo.bar();
}
  catch  (e) 
{
    
if  (e  instanceof  EvalError) 
{
        alert(e.name 
+   " "   +
 e.message);
    }
  else   if  (e  instanceof  RangeError)  {
        alert(e.name 
+   " "   +
 e.message);
    }

    
//   etc
}

Error具有下面一些主要属性:

  • description: 错误描述 (仅IE可用).
  • fileName: 出错的文件名 (仅Mozilla可用).
  • lineNumber: 出错的行数 (仅Mozilla可用).
  • message: 错误信息 (在IE下同description)
  • name: 错误类型.
  • number: 错误代码 (仅IE可用).
  • stack: 像Java中的Stack Trace一样的错误堆栈信息 (仅Mozilla可用).
因此为了更好的了解错误信息我们可以将catch部分改为如下形式:

try   {
    foo.bar();
}
  catch  (e) 
{
    
if  (browserType  !=  BROWSER_IE) 
{                            
        alert(
            
" name:  "   +  e.name  +

            
" \nmessage:  "   +  e.message  +
            
" \nlineNumber:  "   +  e.lineNumber  +
            
" \nfileName:  "   +  e.fileName  +
            
" \nstack:  "   +  e.stack);        
    }

    
else   {                    
        alert(
            
" name:  "   +  e.name  +
    
            
" \nerrorNumber:  "   +  (e.number  &   0xFFFF +

            
" \nmessage:  "   +  e.message " );        
    }

}

JavaScript中的throw命令事实上可以抛出任何对象,并且我们可以在catch接受到此对象。例如:

try   {
    
throw   new  Date();     //  抛出当前时间对象

}
  catch  (e)  {
    alert(e.toLocaleString());    
//  使用本地格式显示当前时间

}

posted @ 2006-09-05 17:56 BennyBao 阅读(1993) | 评论 (1)编辑 收藏

本文着重讨论的是具有RIA特征的Web应用。例如目前比较流行的的Ajax类Web应用。传统的基于纯HTML的Web应用不在本文讨论之列。

随着Ajax的升温,开发人员逐渐对Web应用中的各种UI控件和开发框架开始有了越来越浓厚的兴趣。目前所知的这方面的控件集或开发框架可以说是并不鲜见。笔者将这些产品大致分为两个大类:离散控件集型和数据模型驱动型。这两个词大家应该很陌生,因为他们都是鄙人自造的。

离散控件集型 - 此类产品以提供一系列相对独立的界面控件为主要目的。控件的类型比较全面,例如搭建Web应用常见的各种Grid、Tree、Menu、ToolBar、Window等。不过此类产品一般不会过多的考虑界面中的数据和操作逻辑的封装,至多只会提供相对简单的静态数据绑定*。我认为此类产品的主要出发点是改善Web应用的界面表现能力,同时借助自带的SDK提供一种更加规范的开发模式。
目前我所知的大部分产品似乎都属于这一类别。例如: backbase、qooxdoo、NetAdventage、bindows等。
Backbase实例中心:
http://www.backbase.com/demos/explorer

数据模型驱动型 - 此类产品除了要提供一组比较好用的UI控件集之外,更会提供对界面中数据模型的管理功能。其UI控件以数据敏感控件为主。数据敏感控件可以通过于数据模型的绑定来实现对表现层中数据的展示和控制。这种数据绑定可成为动态数据绑定*。可以说这一类产品的主要出发点除改善Web应用的界面表现能力外,也非常注重提供一种快速开发的模式。
好的数据模型驱动型的开发框架应该首先包含离散控件集中的各种功能,它事实上是一种相对于单纯的UI控件集而言更高层次的抽象。
o_binding.png
这种模式其实在以前CS下非常常见,例如VB、Delphi等RAD开发工具提交数据库应用开发模式都属于这种类型。不过到了BS下人们似乎都忘记这种开发模式。可能是因为不够见多识广,目前笔者所知的此类产品只有dorado。
dorado的示例中心:
http://sample.bstek.com

对于上面提到的两种数据绑定方式的解释如下:

静态数据绑定 – 是指在控件可以根据指派给他的数据源(往往是XML数据源或简单的数组)自动的提取并展示其中的数据。这种提取过程是主动完成的,当提取过程结束后控件无法继续感知数据源中数据的变化。这事实上是从控件到数据源的拉模式(Pull Mode)。

动态数据绑定 – 是指将控件以观察者的角色注册到数据源(往往是经过封装的私有对象)中。数据源成为被观察者。当数据源中的数据或状态发生改变时会主动通知所有观察者(即绑定的控件),然后再由控件自动提取数据完成展现的更新。这样一旦绑定建立以后控件就可以实时的体现数据源中的最新变化。如果用户利用这些控件对数据或状态做了改变,那么这种改变自然也会通过数据源再实时的通知给所有其它相关的控件。这事实上是从数据源到控件的推模式(Push Mode)。
 
回到关于离散控件集型和数据模型驱动型的讨论。这两种开发框架都有这自己的适用面。笔者认为离散控件集型的开发框架更加适合与一些像论坛这样更加注重展现的应用。而对于那些具有明显数据库应用特性的的Web应用(例如MIS类应用),则数据模型驱动型的开发框架更能发挥它的优势。
得出以上结论的原因是我认为数据模型驱动型的开发框架能够使开发人员将更多的精力投入到界面所需要实现的更能当中,至少在制作页面的前期阶段不必太多的关注界面的表现形式。同时如果能够将更多的界面操作逻辑封装到数据模型对象中,就可以保证在后期当最终用户提出界面的修改要求时,开发人员可以用更小的代价来完成对界面的重构。

让我们来具体分析两个场景:

场景1:一个用惯了CS应用的用户要求开发一个界面来维护公司目前拥有的所有书籍。为了方便的完成对所有书籍的CRUD操作,用户希望以一个Grid控件来完成所有这些操作,同时用户希望能够在界面批量的完成一系列C、U、D操作之后一次性的对数据进行保存。每本书籍都有一个由系统自动分配的编码作为主键,因此用户不需要看到书籍的编码。
分析:如果我们现在只有一个离散的Grid控件。要完成上述功能我们还需要做以下一些工作:

  • 由于编码不在Grid中显示,因此找到一个办法能够管理每本书籍的编码。
  • 由于客户端需要缓存用户的一系列C、U、D操作然后作批量的提交处理,因此必须做一些工作以便记录下哪些书被修改了、哪些是新增的、哪些被删除了。
  • 在提交时将所有的数据修改信息抽取出来组装成可用于提交的格式。

    可见如果使用一个离散的Grid控件来制作这个界面,我们还必须要做不少工作。如果我们能够选择一个数据模型驱动型的开发框架,上面提到的很多功能框架中往往已经具备。开发人员要做的往往只是声明好一个数据模型然后把它跟Grid关联起来。如果您以前使用过VB或Delphi这一类开发功能,应该不难想像这个过程。

    场景2:想像一个用户信息的录入界面,如下图。使用者需要输入用户的身份证,由于什么证的号码中包含了很多信息,系统完全有可能从其中解析出出生日期和性别这样的信息。因此为了方便录入,我们可以让表单中的出生日期和性别这两个栏位支持自动填入缺省值的功能,只要用户录入了身份证号码,就可以马上自动填充上述两个栏位。

    o_user_form1.png
     
    在基于离散控件的编程方式中,我们需要知道身份证、出生日期、性别这三个编辑框的id,并针对他们进行编程。其代码形式可能如下:

    var id  =  inputId.getValue();  //  获得身份证号码
       //  对身份证进行解析
    inputBrithday.setValue(brithday);  //  为出生日期设置缺省值
    radioGroupSex.setValue(sex);  //  为性别设置缺省值

    在基于数据模型驱动型框架的编程方式中,我们并不需要关注界面上摆放了什么控件,只需要知道关注如何操作数据模型对象。其代码形式可能如下:

    var id  =  dmUser.getValue( " id " );  //  从数据模型(dmUser)中提取身份证号码
       //  对身份证进行解析
    dmUser.setValue( " birthday " , brithday);  //  为出生日期设置缺省值
    dmUser.setValue( " sex " , sex);  //  为性别设置缺省值

    可见在这种开发模式中我们的代码几乎完全针对数据模型展开,当我们为dmUser中的brithday和sex赋值后,相应的数据敏感控件会立刻自动显示出这些的数据。这样的编程模式可以让代码有高度的一致性,当我们制作复杂的用户界面时,可以不需要记住诸多的控件id。
    进一步假设。如果用户有一天觉得这样的界面并不方便对多笔数据进行方便的维护,而要求对界面进行如下调整。在删除原先的表单,利用一个Grid控件来对用户信息进行维护。
    o_user_form2.png
    如果我们的编程方式是基于离散控件的,那么我们不可避免的要对先前编写那段代码做一些调整了。我需要将那段代码移植到表格当中。
    但是如果我们的编程方式是基于数据模型驱动型框架的,那么我们要做的只是将界面上的表单删掉,然后在放置一个与现有数据模型绑定的Grid控件。至于那段代码,它完全不需要做任何变动。

    综上可见,在MIS类Web应用的表现层开发方面。数据模型驱动型的开发框架可以为开发人员带来更多的实惠。不知道随着时间的推移这一类的开发框架会不会丰富起来?

  • posted @ 2006-09-03 00:26 BennyBao 阅读(2187) | 评论 (9)编辑 收藏