写点什么

高效率的超大规模 Flex 开发

2009 年 11 月 08 日

Adobe Flex 开发与传统的 Web 开发有很多不同之处。正确的理解和利用这些不同之处,可以帮助我们创建更丰富的用户体验,也可以反过来增加网站的可用性以及浏览和更新速度。Flex 还提供了大量的组件和技术库来让 Web 开发更加轻松——它提供的强大工具要远远超过传统 Web 2.0 中的异步 JavaScript 和 XML(Ajax)应用。此外,Adobe 公司最近发布的 Adobe Flash Builder 4 beta(以前叫 Adobe Flex Builder )中提供了许多新的和改进的工具,他们可以用来开发大规模的 Flex 应用程序。Flash Builder 4 着重于提高开发人员的生产力以及设计人员和开发人员的工作流。

Flex 开发中的关键要素之一是使用模块和运行时共享库(Runtime Shared Libraries,RSLs),这些模块和库可以方便系统不同部分的并行开发以及客户端上高效的内存管理等等。另外一个关键要素是使用 Sprint BlazeDS 集成,它大大简化了后台不同技术服务器端的开发和集成,如使用 Java 消息服务 (JMS) 和 Spring 安全进行的通信。

以下是 Flex 开发中的一些关键要素:

  • 利用 Flex 的不同之处。了解为什么 Flex 开发与传统 Web 开发有所不同,并利用那些不同之处让应用程序从中获益。
  • Flex 模块。了解 Flex 模块以及怎样模块化 Flex 开发和部署。使用 Flex 模块来解耦项目中的不同层次。
  • 了解反模式和模式。 预先了解正确的应用程序反模式,这样你可以按照正确的架构模式实现优秀的设计。
  • 使用 Spring BlazeDS 集成项目简化开发。使用 Spring BlazeDS 集成项目简化 Java 服务器集成。
  • 有效地设计。与设计团队紧密合作,充分利用 Flex 和 Adobe Flash 的优势。
  • 测试. 预先计划测试。

是什么让 Web 开发与 Flex 有所不同?

与传统 Web 应用程序不同,Flex 应用程序开发更像传统的桌面应用开发。例如,整个应用程序会被下载到客户端——这是一个庞大的下载,而且取决于应用程序如何组织。当然,有许多方法可以优化这个过程,例如使用组件库或将工程分解成各个模块。

这套方法与传统的超文本标记语言(Hypertext Markup Language,HTML)编写的应用程序不一样——即使它是用 Ajax 开发的。一个 HTML 的 Web 站点通常可以划分成不同的页面,以使得下载量非常小。并且即使使用 Ajax,下载量还是相对较小,因为大部分 Ajax 库本身就相对较小。使用这种方法可以让单个页面的下载量比整个应用程序更小。

Flex 与传统 Web 应用程序开发的另外一个不同之处在于,状态主要在客户端上维护。这超出了在 Web 应用程序上使用 Ajax 控件,因为后者一般只在 Web 浏览器上维护少量的客户端状态。而现在,应用程序中的所有状态都在局部变量中进行维护。

如果你还不太熟悉 Flex 的话,另外一个不同之处可能对于你来说是一个挑战。Flex 使用了一个高度的事件驱动编程风格,主要原因是 Adobe Flash Player 是单线程的,因此任何需要长时间运行的调用都需要注册一个回调函数。单线程对于图形化用户界面(GUI)开发来说是有利的,因为它不存在死锁或者一个线程窃取所有 CPU 周期,从而保持了 GUI 的响应。单线程还使得开发更加简单,因为你不需要处理多线程编程。(还记得在 Swing 中处理线程的噩梦吗?)

Flex 中用以处理事件的主要机制是函数指针,或者作为函数参数进行传递的闭包。这些指针可以是用户为了某个异步事件结束时发生的事件或者提醒而注册的。(函数指针对于 Java 程序员来说很有挑战,因为核心 Java 语言还不支持闭包。)

Flex 模块至关重要

Flex 提供了一些选项可以将应用程序划分成模块,包括创建库以及主应用程序的子模块。通过将项目设计成层次化的形式,开发和编译都可以在单个的分支中进行。

模块化 Flex 项目的主要好处在于,这么做可以优化客户端上下载和启动的时间。应用程序的一小部分可以预先下载到客户端,而剩下的部分可以在后端按需很进行加载。此外,模块还可以被加载和卸载,以减少客户端上的内存占用量。

模块化 Flex 项目的另一个主要好处是,它可以让开发过程更容易模块化。它允许开发人员在不同的模块和库上进行工作以避免开发团队的死锁发生。

然而构建大型 Flex 应用程序的一个挑战之处在于,编译过程所花的时间要比大部分程序员熟悉的 Java 编译时间要慢很多。重新编译一个大型 Flex 应用程序可以花上好几分钟。想要优化这个过程,你可以创建分离的库并且只编译你当前工作的应用程序部分。

一种方法是采用 SWC 文件形式的库。SWC 是 Flex 中使用的主要存档格式,它有点类似于 Java 中的 Java 存档(JAR)文件。你可以使用 SWC 档案来完成一些工作:

  • 组件库。 组件库是一个 SWC 文件,它包含 Flex 应用程序中使用的类和其他资源。
  • 主题。主题定义了应用程序的观感。它包含主题所需的资源(例如图片文件和字体),以及定义这些资源如何被使用在应用程序中的层叠样式表(Cascading Style Sheet,CSS) 。
  • 资源包。这些都是本地化的属性文件和 Adobe ActionScript 类集。

有两种方法可以在项目中添加 SWC 文件:要么静态地连接它们,这种情况下它们会编译进你的项目;要么把它们当做运行时共享库(RSL,Runtime Shared Library )来使用。使用多个 RSL 的好处在于它们可以被分开下载和缓存到客户端上。此外,如果客户端有多个模块的话,它们还可以共享同样的 RSL。

你还可以将主应用程序进行模块化。一种方式是采用 Flex 模块;另一种是使用子应用程序。子应用程序的好处在于它们独立于主应用程序进行开发和测试。

注意当编译模块和库时,Flex 并不像 Java 一样层次地遍历整个树。相反它只编译链接到主应用程序中的部分。这种方式会使得模块和库的开发非常微妙,因为你可能注意不到某个文件被链入了。当进行库开发的时候,有必要定义哪些文件会被包含。

库具有相对直接的可被利用的优势。本质上你是向 Flex 模块管理器传递了想要加载模块的 URL。

复制代码
protected function getModuleFromServer(url:String, onSuccess:Function) :void
{
var module:IModuleInfo = ModuleManager.getModule(url);
module.addEventListener(ModuleEvent.READY, loadModuleSuccessHandler);
module.load();
}
public function loadModuleSuccessHandler(moduleEvent:ModuleEvent) :void {
var module:IModuleInfo = moduleEvent.module;
.... handle initializing the module
}

像这样进行模块加载的有趣之处在于你可以指定另一个 URL 来加载这个模块——例如,在某个故障情况下,客户端丢失了与主服务器的连接。这时客户端可以继续从另外一个 URL 中加载模块,以维护重要的业务功能。

让应用程序轻松地开发和测试

通过适当地激活 View 栈(Stack),你可以用单个 URL 轻松地访问应用程序的任何点——这一功能拥有超越单纯可用性上的优势。除此之外它还使得应用程序上的工作变得很轻松。一个常见的错误发生在你改动部分应用程序,而这个应用程序需要在好几个屏幕和样式中进行导航。如果你不得不为每一处改动而手工进行导航时,这个过程将非常耗时。取而代之,我们可以使用一个固定的 URL 来访问应用程序的不同部分,这将大大减轻开发中的痛苦并且加速整个过程。相同的模式同样可以让应用程序变得很容易测试,因为应用程序单个部分可以通过 URL 来访问和测试。

在 Flex 中,有两种方法可以将视图栈构建到应用程序中。一种方法是使用状态(states),而另一种方法则是使用一个 ViewStack 组件(ViewStack.mxml)。视图栈只是用来显示对象的栈:这些对象可以是一个包装简单的表格或是一个复杂组件的画布(canvas )。使用视图栈的好处在于你可以通过在代码中设置当前的子组件来轻松的操作它们。例如 myViewStack.selectedChild=accountInfo 将会设置视图栈去显示 accoutInfo 视图。这个功能可以让你在测试和开发中轻松地操作视图栈。

客户端架构:反模式

在软件编程中,会有一个通用的范式来定义软件开发中的反模式和模式。模式是解决常见问题的重复解决方案;反模式则是不熟悉语言的程序员最会发生的常见错误。定义好 Flex 中的模式和反模式后,我将会展示一些新手 Flex 程序员常犯的错误,然后再展示一些 Flex 编程开发的最佳实践。

这些实践可以帮助项目在增长为大规模的过程中,避免常见的错误。第一个通用的反模式是将所有的 MXML 视图放在根包中。这么做的好处不是很明显,因为你可以将 MXML 文件放入到包中,并且通过它们的名字空间(由于实际的包的名字可能没有在文件中指定)进行引用。其实 MXML 文件是根据其所在的目录结构来放入包当中的。因此将 MXML 文件放入到根包中等价于不将它们放入任何包中,这样做会使一个大型项目变得一团糟。

举一个例子, Flex 商店示例不仅将主应用程序文件放入到根包中,还将主页,商品页和支持页(HomeView.mxml, ProductsView.mxml, SupportView…mxml)都放入根包中。这个过程和把所有根目录中的视图包放入一个 Java 包中比较类似。

开发人员在创建第一个应用程序时常常会碰到一个问题:那就是如何用视图来捆绑住应用程序模型。一个通用的反模式是在类之间传递模型或者在调用视图前设置模型。反模式的一个例子——也来自于 Flex 商店——即在主应用程序中将目录传递进 ProductsView。

复制代码
<ProductsView id="pView" label="Products" catalog="{catalog}"

这段代码在小的例子中可以正常工作,但是随着应用程序逐渐变大,它会带来一些问题,如层之间的紧耦合,以及很难开发和测试。另外一个问题是当同样的数据需要在应用程序不同部分显示时,这些数据需要分开地传递给每一个组件。首先,这看起来似乎不是一个问题,但是试想下如果尝试模拟每一个传递和使用数据的实例,结果会怎样。

客户端架构:推荐模式

多年来众多的 Flex 架构和框架已经被开发出来。最有前景的一种方法使用了一个像 Swiz 一样的轻量级依赖注入框架,并用它来划分项目结构。你可以在 Swiz 中定义类似于 Spring 中的应用程序 bean,并将它们注入到应用程序中的不同部分。一个典型的应用程序框架会包含一个中心类,这个类会定义服务怎样组合在一起——例如,ClientBeans.mxml 类为应用程序声明了通用的模型和控制器:

<model:StoreModel id=”storeModel” />

然后 View 类连接上控制器:

复制代码
[Autowire(bean="storeModel")]
[Bindable]
public var storeModel: StoreModel;

对于控制器和其他通用组件,你也可以这么做。用这种方式组织项目结构有一些优势。一个优势在于它可以让模拟服务轻松地植入控制器,以促进开发和测试。另外一个优势在于它能够除去和客户端架构不同层之间的耦合性。

一个精心设计的主视图状态有助于实现前面提到的可用性和可测试性目标。视图栈的思想是让应用程序中的任何视图可以通过单个 URL 进行访问。除此之外,视图栈的参数需用通过通用控制器传入,这个通用控制器能够让你在应用程序中各个地方使用通用的 URL 和必要的参数来访问期望的视图。

一个 Flex 架构示例:重构 View Store

作为架构的一个示例,让我们先看看怎样将 http://www.adobe.com/devnet/flex/samples/flex_store/ 上的 Flex Sotre 的例子进行重构。

首先,将主源代码移动到子目录中。在这个例子中,我将所有的资源,产品视图,以及样例目录移动到了 flexstore/src/flex 中并将其作为源代码主目录。我还把所有的 MXML 和 CSS 文件移动到了这个目录中。

接下去,将三个主视图类移动到视图文件夹中。在 src/flex 目录下创建一个 com/anvilflex/view 目录。将 HomeView.mxml,ProductsView.mxml 以及 SupportView.mxml 文件放入到这个目录中。为此,你必须对 flexstore.mxml 做出如下改动:

  • mx:Application 标签中加入 xmlns:view=“com.anvilflex.view.*” 。
  • 将视图变为使用标签(因此将变成 <view:HomeView … />)。

将 Product.as 类移动到分开的域文件夹中——com/anvilflex/view。为此,你必须修复 Product 类的 import 语句,并删除 Product 单词的最后一个字母。接下去,按住 Ctrl- 空格键来使用自动完成功能,它同样也可以为 Product 类加入正确的 import 语句。

最后,引入 ProductControl 来管理产品页面和结算间的过渡。

使用 Spring BlazeDS 集成简化 Java 开发

Spring BlazeDS 集成项目大大简化了与客户端交互的 Java 服务端代码开发。在最新发布的项目中,团队成员可以实现核心的配置文件,如 web.xml 以及 Flex 服务配置文件。接下去,团队人员需要简单地了解一下如何在他们的类中增添适当的标注来将它们作为服务展现给 BlazeDS。举个简单的例子,你可以将 Product 服务类展现为一个远程服务:

复制代码
@Service("productService")
@RemotingDestination(channels={"my-amf"})
public class ProductDAO {
@Autowired
public ProductDAO() {
//... initialize the ProductDao class
}
@RemotingInclude
public Product findProductName(int id) {
Product product = database.findProduct(id);
return product.name;
}

在这个例子中,我将 ProductDAO 类声明为了远程服务,它可以通过使用 productService 进行调用。单个方法可以使用 @RemotingInclude 将其展现为远程方法。其实这些方法都会作为远程服务展示给 BlazeDS,其中 Flex 可以通过远程对象调用远程服务。

与图形设计团队一起工作

刚开始的 Flex 项目中一个常见的问题是,确保图形设计团队了解 Flex 中可以做什么。Flex 为很多有趣的设计提供了可能性:图形设计团队需要了解 Flex 中可以做什么,以及 Flex 怎样来表示一个基础设计转变。

通常情况下,碰到的问题是,图形设计团队用一些像 Adobe Photoshop 的工具创建了一个模拟。尽管设计看起来非常好,但是它与工作中的应用程序还相距甚远。整个设计将不得不削减并且颜色和字体都得与应用程序的 CSS 相匹配。根据设计的复杂度,自定义的 Flex 组件也需要被创建。这些组件可能是需要自定义皮肤的按钮,也可能是定义了一系列新功能集合的组件。困难之处在于设计和实现之间变得极其耗时,设计人员需要搞清楚什么是可能的并从设计中实现这些变化。每一次迭代都会变得更加复杂。

幸运地是,Adobe 已经引入了一个新的工具来改进这个过程,这个工具就是 Flash Catalyst。它可以让类似 Photoshop 这样工具产生的艺术品能够轻松地转变为可用的原型。它还可以创建一个原型的 GUI 并最终应用到应用程序中。通过使用此工具,设计人员可以更快地遍历他们的设计,从而使该组织能更有效地调整设计,以满足其要求。

单元及功能测试

Flex 开发中有两种主要的方法来测试一个应用程序。第一个是使用 funit 或者 fluint 的功能测试。这个方法可以进行服务以及控制器的测试。第二个方法是功能测试,它可以测试与 GUI 间的实际交互。功能测试可以使用像 Mercury 一样的商业工具,或者像 FlexMoneky 一样的开源工具。

两种类型测试的关键是要在松耦合层创建可以独立测试的代码。例如可以通过将默认捆绑改变为使用模拟服务,来轻松地修改服务。

总结

使用 Spring BlazeDS 集成的 Flex 为大规模企业应用程序提供了一系列卓越的技术。开发人员的挑战在于设计模块化,易调试,可扩展的架构。这里的可扩展,我指的不仅仅是软件,还包括开发过程。解决这些挑战可以帮助项目成功,并为组织带来一个更高的投资回报率。

查看英文原文: Super-sized Flex Development—Without the Extra Calories


译者介绍:曹如进,计算机软件与理论专业硕士研究生,主要研究方向包括算法设计与分析,语义网。目前对函数式编程语言,富客户端开发比较感兴趣。InfoQ 中文站内容团队,尤其是架构、SOA 和 Ruby 社区需要您的参与,有意者请邮件至 editors【AT】cn.infoq.com

2009 年 11 月 08 日 22:0712519
用户头像

发布了 125 篇内容, 共 29.1 次阅读, 收获喜欢 2 次。

关注

评论

发布
暂无评论
发现更多内容

演讲经验交流会|ArchSummit 上海站

演讲经验交流会|ArchSummit 上海站

高效率的超大规模Flex开发-InfoQ