Flex应用架构:Presentation层架构实践 (第一篇) ── 问题和错误的做法

作者:Adobe技术专家董龙飞,微博:http://t.sina.com.cn/donglongfei

原文地址:http://www.donglongfei.com/?p=137

RIA应用架构:Presentation层架构

RIA应用架构:Presentation层架构

Flex应用中Presentation层要解决的问题

在一个使用Flex RIA应用作为企业应用客户端解决方案的项目中,从整体项目角度看,与服务器端的业务逻辑相比,Flex应用本身就已经被看作为展示层(Presentation层)。然而,如果仅仅把思路锁定在Flex RIA应用本身,其内部也存在着诸多层级(UI组件需要展示,交互,显示数据,数据需要从服务器端获取,用户交互后需要提供响应,为了响应有需要执行一系列本地或远程操作,而为了提供良好的体验又需要给客户提供动画或特效一类的可视化提示),作为一个前端应用的开发工程师,实际上需要解决的问题真是不少。

幸运的是,我们可以非常完美的在这里应用MVC架构。MVC的View就是我们将要讨论的展示层。

问题一:解耦

错误的做法

典型的Flex RIA应用中,我们使用MXML编写可视化界面,在其中通过标签实例化UI组件、完成页面布局、应用样式,实现动画,我们又在Script标签中编写代码获取数据、实现UI组件的逻辑,处理事件,通过绑定完成数据和组件之间关联,并交给Flex协调数据变化和视图的更新。在这种结构中,存在着最大的问题就是紧耦合。从直观角度看,一个MXML文件中嵌有大量的ActionScript代码。这当然不变维护,难于测试,无法重用。

错误做法的例子

让我们通过一个例子来展示这点。

我们的例子叫做“10000小时”。例子的灵感来源于马尔科姆.格拉德威尔的《异类》(《Outliers》)这本书。

一个人的技能要达到世界水平,他的练习时间就必须超过10000个小时 ─ 任何行业都不例外。

—- 《异类》

我们在这里要做的只是例子的一个基本功能:维护一个10000小时目标的列表。我们将使用一个表格显示要完成目标的列表,用户点击表格中的条目能够将对应的目标显示在对应的表单中,用户可以更新数据。更新的数据会自动的同步到表格中。

下面是应用的例子:

RIA应用展示层例子

RIA应用展示层例子

主要视图的代码如下:

  1. < ?xml version="1.0" encoding="utf-8"?>
  2. <s:vgroup xmlns:fx="http://ns.adobe.com/mxml/2009"
  3. xmlns:s="library://ns.adobe.com/flex/spark"
  4. xmlns:mx="library://ns.adobe.com/flex/mx" verticalAlign="middle" horizontalAlign="center" creationComplete="init(event)">
  5. <fx:script>
  6. < ![CDATA[
  7. import com.mark.tenthousandsHours.domain.GeniusTarget;
  8. import com.mark.tenthousandsHours.infrastructure.service.FakeGeniusTargetService;
  9.              import mx.events.FlexEvent;
  10.               import spark.events.GridSelectionEvent;
  11.               //获取数据的服务
  12. private var service:FakeGeniusTargetService=newFakeGeniusTargetService();
  13.   [Bindable]
  14. private var targets:ArrayCollection;
  15. [Bindable]
  16. private var selectedTarget:GeniusTarget=new GeniusTarget();
  17. [Bindable]
  18. private var isGeniusTargetChanged:Boolean=false;
  19.   //显示表格数据
  20. protected function showFormData(event:GridSelectionEvent):void
  21. {
  22. // TODO Auto-generated method stub
  23. selectedTarget=dgTargets.selectedItem as GeniusTarget;
  24. isGeniusTargetChanged=true;
  25. }
  26.    //提交更新
  27. protected function formSubmit(event):void{
  28. service.update(selectedTarget);
  29. isGeniusTargetChanged=false;
  30. }
  31.   protected function init(event:FlexEvent):void
  32. {
  33. // TODO Auto-generated method stub
  34. targets=service.getGeniusTargetList();
  35.   }
  36.   ]]>
  37. </fx:script>
  38. <fx:declarations>
  39. <!-- 将非可视元素(例如服务、值对象)放在此处 -->
  40. </fx:declarations>
  41. <s:datagrid id="dgTargets" dataProvider="{targets}" selectionChange="showFormData(event)" width="100%">
  42. <s:columns>
  43. <s:arraycollection>
  44. <s:gridcolumn dataField="id" headerText="id"/>
  45. <s:gridcolumn dataField="gpTargetName" headerText="目标"/>
  46. <s:gridcolumn dataField="gpTargetHours" headerText="计划联系小时数"/>
  47. <s:gridcolumn dataField="gpExercisedHours" headerText="实际练习小时数"/>
  48. </s:arraycollection>
  49. </s:columns>
  50. </s:datagrid>
  51. <s:form defaultButton="{btnSubmit}" id="stackedForm" backgroundColor="#EEEEEE" >
  52. <s:formheading label="{'10000小时:' + selectedTarget.gpTargetName}" backgroundColor="haloSilver" width="100%" />
  53.   <s:formitem sequenceLabel="1." label="10000小时目标:" required="true">
  54. <s:textinput id="txtTargetName" maxChars="64" text="@{selectedTarget.gpTargetName}"/>
  55. </s:formitem>
  56.   <s:formitem sequenceLabel="2." label="计划小时数目" required="true">
  57. <s:textinput text="@{selectedTarget.gpTargetHours}" id="targetHours"/>
  58. <s:helpcontent>
  59. <s:label text="为了完成目标,所计划的小时数目" baseline="24"/>
  60. </s:helpcontent>
  61. </s:formitem>
  62. <s:formitem sequenceLabel="3." label="已经练习小时数">
  63. <s:label text="{selectedTarget.gpExercisedHours}"/><br />
  64. </s:formitem>
  65.   <s:formitem>
  66. <s:layout>
  67. <s:horizontallayout />
  68. </s:layout>
  69. <s:button id="btnSubmit" label="确定" click="formSubmit(event)" enabled="{isGeniusTargetChanged}" />
  70. <s:button id="btnCancel" label="取消" click="formSubmit(event)" enabled="{isGeniusTargetChanged}" />
  71. </s:formitem>
  72. </s:form>
  73. </s:vgroup>

关于代码例子的简单说明

在这个例子中,

  • 1.行50至行59:使用Spark Datagrid显示列表数据,数据来源自一个Service类获取到的ArrayCollection。如果你希望这个错误的例子更加糟糕,可以把Service类的实现也写在这个MXML中。用户选择表格中的条目时,会触发selectChange事件,进而调度侦听器方法showFormData()。
  • 2.行61至86:使用Spark Form显示表单,Form中的UI组件绑定在selectedTarget对象上,该对象由showFormData()更新。

尽管我们没有完成所有的功能,但这个例子工作的还是非常不错的。最棒的是,编写这样一个例子只需要你大概15分钟,而且对于一个比较熟练的Flex开发者来说,基本不用动脑。

但事实果真如此吗?

很明显,我们没有把Service类的实现写在MXML中。此外,在真实的项目中,我们需要为这个可视化界面实现的功能远不止更新。如果我们需要的数据表格功能更复杂,如果我们需要为表单实现更多的操作功能,比如新增记录,弹出窗口警告等等,那么我们就要不断的在其中加入更多的代码。总之,用户的需求最终会把这个简单的功能搞得复杂无比,你会非常不幸地看到一个无比冗长,乱麻一团的MXML文件。(不过,诚实的讲,它还能工作,这个确实很棒。这也许能解释为什么许多开发者愿意使用这种简单的代码架构完成应用。)

更糟的事情还在后头,在整个项目中,我们希望能够在另一个界面中使用相同功能和相同交互行为的数据表格,嗯,没错,在另一个地方,我们还希望能够使用同样的表单。更加过分的是,我们希望能够在其他的项目中使用我们业已开发好的数据表格和表单。

没错,这确实糟透了。等一等,我们还没有说到测试。OK,我们就此打住。

设计模式:

在本文章的Part2,我会给大家介绍许多应用架构中解决上述问题的最佳实践 : 设计模式Presentation Model和Presenter,我将向你解释什么时候使用Presentation Model,什么时候使用Presenter,我们将通过重写上面这个示例来为你解释如何实际应用这些模式,然后进一步向你解释这些模式不适用于哪些问题,这些问题又该如何解决

最终,我希望你能够明白,设计模式如何帮助我们构建RIA应用的架构,并能够进一步探索并应用更多的模式。

riadevID: 
您给予的分值: None 平均分: 6.2 ( 5 票)

发表新评论

  • 网页地址和电子邮件地址将会被自动转换为链接。
  • 行和段被自动切分。
  • 您可以使用下面的标签来高亮显示您的评论内容: <code>, <blockcode>. 可以使用"[foo]".旁边显示标签样式 "<foo>" PHP代码可以用这样的区块来包含<?php ... ?> or <% ... %>

更多格式化选项信息

验证区域
系统验证:请回答下面的问题