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

2006年9月8日

收藏一篇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 阅读(194) | 评论 (0)编辑 收藏

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

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

2006年9月5日

在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 阅读(1879) | 评论 (1)编辑 收藏

2006年9月3日

本文着重讨论的是具有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 阅读(2058) | 评论 (9)编辑 收藏

    2006年8月30日

    本文着重讨论的是具有AJAX特征的WEB应用表现层的设计模式,特别是如何设计表现层中展现数据的管理模式。同时本文假设应用的用户界面是操作性和交互性相对较强的MIS类应用,因此其中的部分观点可能并不适合以内容发布为主的互联网应用。

    被过度倚重的AJAX

    自打2005年初第一回听说AJAX以来,这个名词以飞快的速度传播,走红的速度勘比李宇春。AJAX即不是新技术也不是很复杂的技术,它不过是基于WEB的RIA应用的一个操作特性(或技术特性)而已。这一切的发生也许因为是AJAX出现的时机,那正是用户为了WEB应用那令人不堪的操作性即将抓狂,程序员为了难以实现的页面操作逻辑而即将崩溃之际。于是AJAX成了BS应用的救命稻草。颓废的人们看到了希望的曙光。

    不过以鄙人拙见,大家如此热衷于讨论AJAX似乎有点本末倒置了。AJAX本身并不能帮我们上面提到的问题。我们需要应该一套完整的UI组件库,象VB,DELPHI、PB中的我们曾经用过的那种组件库,一套易于使用有能够与各种开发模式的对接的组件库。当然最好是带有AJAX特性的组件库。但是现在我们好像还没有得到一套令大家都满意的组件库,于是我们仍然只能讨论AJAX聊以充饥。

    言归正传,要设计这样的一套UI组件库还有很多障碍需要逾越。

    1.       DHTML+JavaScript的组件开发可不像在CS中那样简单。
    2.       如何有效的管理展现数据。
    3.       让性能不在成为瓶颈。
    4.       跨浏览器的兼容性。
    5.       好用的设计工具。
    … … …

    浏览器端的MVC 将部分表现层逻辑推向前端

    在上面提到的几个障碍当中我感觉最少被大家提及的就是第二条。拜读过dlee推荐的<Ajax in Action>,其中提到了在浏览器端应用MVC。不过书中只提到了数据与展现的分离,却并没有提到如何有效的管理这些用于展现的数据。V自不必想,一定是指运行于浏览器中的各种可视化的控件;M应该是指用于展现的数据;而C应当是指介于M和V之间的松耦合的关联关系。我想“浏览器端应用MVC”在其合理性上应该能够得到大家的共识,如同我们讨论Server端的MVC架构模式时一样,从M的设计开始是我个人的习惯思维,应当也是浏览器端MVC中的重中之重。首先设计出一个健壮的表现层中的数据管理模式将为后面的工作打下一个良好基础。
     

    参考文档 : [原创] Web表现层的Client端设计模式探讨

    这里提到的表现层应当是指包含Server端的展现相关的逻辑以及浏览器中的逻辑。在传统的开发方式中表现层逻辑往往只涉及到Server端,而到了Client端已完全变成了HTML+CSS或XML+XSLT毫无设计模式可言。笔者认为随着技术的发展和AJAX的推动,我们有必要将一部分表现层逻辑推向浏览器已进一步增强界面的交互能力。

    或许目前大家设计的WEB页面还很少需要考虑对展现数据的管理。但是一旦有一天我们拥有了一套好用的UI组件库,那时我们设计的用户交互界面的复杂度也就会突破目前我们习惯认为的上限。想像一个稍微有点复杂的场景,如果我们拥有了一个像Excel一样的可以对任意单元进行实时编辑的Grid组件,用户可以对其中的数据做任意的增删改操作,那么我们就必须要考虑一下如何将用户所填入的数据以合理有效的方式提交回Server端了。
     
    CS中的展现数据模型对象

    不过真的要来设计一种在表现层中的数据模型,还真是有点千头万缕、无从下手。所以在具体考虑如何管理这些数据之前,我们先来看一看在传统的CS应用中数据是如何进行管理的,有没有什么可以借鉴的东西?不约而同的,在这些开发模式中都能找到一种专用的数据模型对象,在VB中它叫ADO.RecordSet、在Delphi中它叫TDataSet、在PB中它叫DataWindow。它们都有一些共同的特点:

    1.       表驱动的结构, 具有当前记录的概念。
    表驱动的设计模式是由关系型数据库自然衍生过来的设计方式,这种设计非常有利于用户对数据的浏览和编辑,也符合我们对同结构批量数据进行浏览和编辑的一般理解和习惯。
    o_grid.PNG

    2.       控件可直接与数据模型进行绑定。

    数据敏感控件与数据模型进行绑定,并自动的展示、修改或控制其中的数据,这是CS中最常用的一种开发模式。其核心原理就是设计模式中的观察者模式。数据模型是被观察者,控件是观察者。当数据模型中的数据发生变化时,会主动的通知绑定的控件做相应的刷新动作以实时的体现最新的数据。对于支持数据修改或控制操作(例如:翻动记录的操作)的控件,如果用户利用其对绑定的数据模型中的数据或状态做了改变,那么这种改变自然也会实现通知给所有其它相关的控件中。
    o_binding.png 

    但是在现在的
    Java BS
    应用开发过程中我们往往比较少的用到。即使有绝大部分也只是只读型的绑定。

    3.       弱类型的数据管理方式及列描述对象。
    在上述CS应用中,数据对象大都以类似Map的方式对数据进行管理,而不是像我们在Java中更经常讨论的VO、PO的数据描述方式。对弱类型的数据对象而言,真正的数据类型(相当于VO中属性的类型)是保存在一组列描述对象当中的。
    在Java中我们习惯的读取和设置属性的方法往往是:
    employee.getName(); 
     employee.isMarried(); 
     … … … 
     employee.setName(“Henry”); 

    而在CS的开发的代码中读取和设置属性的方法往往是(以ADO.RecordSet为例):
     dsEmployee(“name”); 
     dsEmployee(“married”); 
     … … … 
     dsEmployee(“name”)  
    =   “Henry”; 

    4.       都具有一定的数据校验功能及支持一些与展现相关的属性。
    数据对象中的列描述对象在描述属性名和数据类型的同时,往往还包含了其它一些跟显示和编辑有关的特性,例如:readOnly、format、validator等。以readOnly属性为例,当我们将数据模型中某个列的readOnly属性设置为true后,所有与该列相关的数据敏感控件都将变成只读的状态。这样的好处在于开发人员在编写页面逻辑时不必过多的考虑页面上堆砌了那些元素,而之需要关注他要处理怎样的数据操作逻辑,页面上大部分的操作逻辑或显示逻辑都将围绕数据模型对象而展开。

    5.       支持事件。它们都拥有类似beforeChange、afterChange、beforeDelete、afterDelete这样的事件,以便于开发人员能够利用这些事件提供一些简单的数据校验或操作逻辑。

    BS中的展现数据模型的初步设想

    CS下的这些数据模型对象在早些年都有着成功的实践。试想,能不能把这种在CS下的表现层设计模式移植到BS的开发当中呢?不过鉴于BS架构更高的复杂的这个任务并不简单,按照我的设想经过移植的系统架构大致可能如下:
    o_arch1.png

    图中数据模型对象被拆分成了两个部分,即Server端的实例和Client端的实例。当我们要将数据从Server端传递到Client端时,系统首先应在Server端构建一个基于Java的数据模型,该数据模型对象的数据取自BO提供的VO。当然,如果Server端原本使用了JSP+Bean的开发模式,数据模型对象也可能直接取自JDBC的ResultSet。而后数据模型对象将利用一套实现已封装好的规则,将数据以XML等方式输出到Client端。此时Client端的可视化控件就可以对数据进行浏览和操作,如果用户通过可视化控件对数据做了修改,这些脏数据也将暂时被缓存在Client端数据模型对象中,知道用户最终点击了提交按钮,此系统再利用AJAX机制将数据同步回Server端并执行进一步的后台处理。
     
    看起来我们好像已经有了一个很好的开局!不过笔者认为在BS架构中完全照搬CS中的那套设计思路可能并不是最佳的方案。主要原因在CS中的这些数据模型都是按照表结构驱动的方式设计的,其基本思路类似关系型数据库中的表。而我们在J2EE的设计模式中更常使用的却是模型驱动的设计方式。表结构驱动自认有它的有点,易于理解,方便使用。不过他也有一些致命的缺陷,特别是它不能很好的描述数据对象之间的关系,每当我们试图使用表驱动的模型来描述递归,树,主从关联这样数据关系,总是会感觉束手束脚。无疑,基于OO的对象模型的结构是一种更好的数据描述方式,它往往能够更加准确的、真实的表达数据之间的关系。 

    (未完待续...) 
     

    posted @ 2006-08-30 01:55 BennyBao 阅读(1806) | 评论 (3)编辑 收藏