要求
预备知识
您应该熟悉 ActionScript 3 和面向对象的术语与准则。此框架的相关经验将有所帮助,但这并非必要条件。
用户级别
中级
必需的产品
如今,在 ActionScript 3 开发框架方面,我们有着诸多选择。这是一种好现象。开源社区生机勃勃,简化开发工作的工具得到积极采用。大型媒体企业、独立游戏开发人员、新手和所有规模的企业都在利用这些工具。
这是 Robotlegs 系列文章的第一部分,后续文章将详述核心 Robotlegs 概念,并介绍一些涉及为与之交互而构建的第三方实用工具和库的更为高级的概念。
什么是 Robotlegs?
简单地来说,Robotlegs 就是一种将对象连接在一起的机制。ObjectA 需要与 ObjectB 通信。ObjectA 不希望或者不需要了解 ObjectB 是否存在。那么它们应该如何通信?最简单的答案就是通过事件实现。利用 Flash,我们拥有一种本机事件系统,专门促进这种类型的通信。就像您在日常工作中使用事件一样,显示列表中的对象会通过事件通信,事件冒泡允许远程对象接收来自其他显示对象的消息。那么未处于显示列表中的对象又会如何?在这种情况下,Robotlegs 这样的框架能够真正地使您的工作更加轻松。
实际上,Robotlegs 就是一组模块化的实用工具和接口,提供的工具可简化这些通信任务、减少重复性的编码任务,同时管理应用程序内的依赖性注入。除了这组核心工具集之外,Robotlegs 还提供了一种略有描述性的 MVC+S(模型、视图、控制器和服务)实现,以便帮助您起步。如果您有过任何 PureMVC 方面的经验,那么就能够迅速适应 Robotlegs MVC+S 实现中的仲裁器与命令的使用,如果您没有这方面的经验也不必担心,我们将在不久之后更加具体地介绍这些类。
这篇文章将利用一个简单的“Hello World”示例,简单介绍 Robotlegs。看到示例之后,您很可能会说:“我完全可以在一个 MXML 文件中完成这一切,而且完全不必这么麻烦!”对于这样一个简单的示例来说,这种说法可能是正确,但不要忘记,在大型项目中,这种结构很快就会体现出自身的价值。这是使用框架开发带来的整体优势。它允许我们有效地沟通概念并理解代码基础,比毫无模式和实践可言的简单堆砌的应用程序更加快捷。
本文不会事无巨细地介绍 Robotlegs 的方方面面,但希望能够激发您的兴趣。我在文章末尾处提供了一些资源,供您进一步探究。闲话到此为止,让我们来看看代码!
Robotlegs MVC+S 应用程序的基本结构
典型的 Robotlegs MVC+S 应用程序由几个部分组成:
- 上下文是发起依赖性注入和 Robotlegs 使用的各种核心实用工具的引导机制。
- 仲裁器管理应用程序视图组件与应用程序内其他对象之间的通信。
- 命令表示应用程序可以执行的各项操作。这些操作通常是为了响应用户活动,但不仅限于此用例。
- 模型存储数据,并表示应用程序的当前状态。
- 服务是您的应用程序通往外部世界的门户。
让我们来更加具体地探讨一下上下文和仲裁器,首先从上下文开始。
上下文是应用程序的核心。它提供了中央事件总线,供您的其他应用程序对象用于彼此通信。除了初始加载和引导应用程序之外,您在正常开发过程中不会用到上下文。它会适时出现、完成自己的工作,随后悄然离开,不妨碍您全心投入开发工作。上下文并不是单一的。您的应用程序可有任意多个上下文,这使得 Robotlegs 极为适合模块化的应用程序。本文不会探讨模块化应用程序,但它将作为未来文章的主题,因为这是一种极为强大的工具。作为开始,让我们来观察最基本的上下文。
HelloWorldContext.as
import org.robotlegs.mvcs.Context public class HelloWorldContext extends Context { override public function startup():void { //bootstrap here } }
在上下文内,您覆盖了 startup 方法。startup() 方法是在上下文完全初始化之后调用的。在幕后,调用 startup() 之前,上下文将创建所有核心 Robotlegs 实用工具的实例,准备接收依赖性输入映射,并创建用于在应用程序的多个对象间通信的事件分发器。
创建了 Context 类之后,您的应用程序就需要引用它。在 Flex 4 Spark 应用程序中,这通常是在扩展应用程序的主 MXML 文件内完成的,如下所示。
HelloWorld.mxml
<?xml version="1.0"?> <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" xmlns:local="*"> <fx:Declarations> <local:HelloWorldContext contextView="{this}"/> </fx:Declarations> </s:Application>
由于 Context 属于非可视类,因此必须放置在 Declarations 标签中,您还应该注意,contextView 属性与应用程序本身绑定。上下文视图是 Context 的根视图,并且用于提供自动帮助,用于查看组件仲裁。稍后在讨论视图与仲裁器之间的关系时,我们将介绍相关内容。
这就是上下文。正如我之前提到的那样,它在应用程序的生命周期内仅仅是暂短存在,但又有着至关重要的地位。在上下文就绪之后,我们就可以添加一些视图组件,并使之通过 Robotlegs 彼此通信。让我们来看看仲裁器及其与应用程序视图组件的关系。
仲裁器位于视图组件与应用程序的其他部分之间。简单地来说,仲裁器侦听事件。在用户与组件交互时,或者在组件通过其他某种方式更新时,您的视图组件将分发事件。必须捕捉到这些事件,并将其交付给应用程序的其他部分。或许用户单击了一个 Save 按钮,因此有一些信息需要发送到服务器。仲裁器侦听此事件的发生,在确实侦听到此事件时,仲裁器将收集恰当的信息,并发送一个事件,应用程序的其他部分可以利用此事件来执行某些数据工作单元。
类似地,仲裁器也会侦听来自应用程序其他部分的事件。如果接收到了来自服务器的某些数据,并且进行了解析,服务类也分发了一个事件,则仲裁器的角色就是帮助您侦听此事件,并使用新数据更新视图组件。这里是一个将接收仲裁器的视图。
MessageView.mxml
<?xml version="1.0"?> <s:TextArea xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx"> </s:TextArea>
这个类非常简短。这仅仅是 TextArea 的一个简单的扩展。为什么不直接使用 TextArea?依赖性注入与明确的类配合得更好。这意味着,通过将 TextArea 扩展到我们的全新 MessageView 类之中,我们就创建了一个特定的视图组件,依赖性注入将据以操作。如果应用程序将有多个不同用途的 TextArea,那么这一点尤为重要。通过按照这种方式划分类,我们就能明确定义类的意图,并允许依赖性注入工具有效地完成自己的工作。对于 MessageView,我们也会在未来添加其他一些功能。就本例而言,它将仍然是一个 TextArea,但您应该了解这一点。现在,我们将观察 MessageView 组件的仲裁器。
MessageViewMediator.as
import org.robotlegs.mvcs.Mediator; public class MessageViewMediator extends Mediator { [Inject] public var view:MessageView; override public function onRegister():void { trace("I am registered!"); } }
此时,MessageViewMediator 有两个有趣的特性。您很快就会注意到使用 [Inject] 元数据标签的第一处。Robotlegs 使用这个标签来识别需要为之执行注入的属性和方法。使用仲裁器时,只要创建了仲裁器,仲裁后的视图就始终可以注入。您不需要为注入而对视图的映射做出任何特殊考虑。在将视图映射到其仲裁器时,它将为您处理这些问题。稍后我们将介绍相关内容,但首先让我们来看看基本仲裁器的第二个有趣的特性,也就是 onRegister() 方法。
onRegister() 方法是在仲裁器完全初始化时为您提供的一个挂钩。注入已经发生、视图已经就绪,通常您将在这里为视图组件和应用程序添加事件侦听器。您通常要在所创建的每一个 Robotlegs 仲裁器中覆盖此方法。
现在,您已经有了一个视图组件和一个仲裁器,它们需要向上下文注册或者与之映射。这是通过 MediatorMap 实现的。正如其名称所暗示的那样,MediatorMap 是一种在上下文内将仲裁器映射到视图组件的实用工具。此外,MediatorMap 默认侦听 contextView 中的 ADDED_TO_STAGE 和 REMOVED_FROM_STAGE 事件,以便在显示列表中添加或删除视图组件时自动创建和销毁对应的仲裁器。在拥有大量(数以千计)显示对象的图形密集型应用程序中,这并无价值,而且这种自动仲裁可能会导致性能问题。在普通应用程序中,这样的自动化则非常便捷,很少会导致性能问题。我们在 HelloWorldContext 内将 MessageView 映射到其 MessageViewMediator 的方法如下。
override public function startup():void { mediatorMap.mapView(MessageView, MessageViewMediator); }
完成这个任务之后,仅剩的工作就是将 MessageView 添加到 HelloWorld.mxml 中。
HelloWorld.mxml
<?xml version="1.0"?> <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" xmlns:local="*"> <fx:Declarations> <local:HelloWorldContext contextView="{this}"/> </fx:Declarations> <local:MessageView top="40" width="100%" height="100%"/> </s:Application>
现在,在调试器中运行您的应用程序时,您将看到控制台上打印了“I am registered!”祝贺您,您已经得到了一个功能完整的 Robotlegs 应用程序。当然,它现在几乎没有任何实际功能,但我们可以改变这种情况。除了单纯的引导之外,为了给我们的应用程序提供其他一些功能,我们将添加一个名为 HelloButton 的按钮,它扩展了 Spark Button 类,另外还会为 HelloButton 添加一个名为 HelloButtonMediator 的仲裁器。
HelloButton.mxml
<?xml version="1.0"?> <s:Button xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx"> </s:Button>
HelloButtonMediator.as
import flash.events.MouseEvent; import org.robotlegs.mvcs.Mediator; public class HelloButtonMediator extends Mediator { [Inject] public var view:HelloButton; override public function onRegister():void { } }
现在,HelloButtonMediator 看起来与上面的 MessageViewMediator 几乎相同,唯一的差别只有类是不同的。在为 HelloButton 及其仲裁器添加了映射之后,您的上下文启动方法将与此类似。
override public function startup():void { mediatorMap.mapView(MessageView, MessageViewMediator); mediatorMap.mapView(HelloButton, HelloButtonMediator); }
您还要为 HelloWorld.mxml 添加按钮,使之能够添加到显示列表中。您可能还要为 HelloButton 标签属性添加一些有趣的内容,但我将这项任务留给您自行完成。
HelloWorld.mxml
<?xml version="1.0"?> <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" xmlns:local="*"> <fx:Declarations> <local:HelloWorldContext contextView="{this}"/> </fx:Declarations> <local:HelloButton label="Say Hello"/> <local:MessageView top="40" width="100%" height="100%"/> </s:Application>
此时,我们就得到了两个完全经过仲裁、急于彼此通信的视图组件。我不是那种会拒绝满足对象的需要的人,因此让我们来完成这个任务——首先从用于通信的自定义事件开始。
HelloWorldMessageEvent.as
public function get message():String { return _message; } public function HelloWorldMessageEvent(type:String, message:String, bubbles:Boolean = false, cancelable:Boolean = false) { super(type, bubbles, cancelable); _message = message; } override public function clone():Event { return new HelloWorldMessageEvent(type, message, bubbles, cancelable) }
这是一个简单的自定义事件。务必在您的自定义事件中覆盖 clone() 方法。如果没有这个方法,事件就无法重新分发、中继或冒泡。我养成了始终在所有自定义事件中覆盖 clone() 的习惯。如果经历过数个小时艰难的调试,那么您也必然会养成这样的习惯。
我们希望在用户单击 HelloButton 时更新 MessageView。HelloButtonMediator 需要侦听 HelloButton 的 MouseEvent.CLICK 事件,随后将 HelloWorldMessageEvent 分发给应用程序。它并不了解谁会响应这个事件。它也不关心谁会响应这个事件。HelloButtonMediator 只会完成自己的工作。
HelloButtonMediator.as
public class HelloButtonMediator extends Mediator { [Inject] public var view:HelloButton; override public function onRegister():void { addViewListener(MouseEvent.CLICK, handleMouseClick) } private function handleMouseClick(event:MouseEvent):void { dispatch(new HelloWorldMessageEvent(HelloWorldMessageEvent.MESSAGE_DISPATCHED, "Hello World")); } }
为 HelloButtonMediator 添加了视图侦听器之后,我们即可将一个事件分发给应用程序。下一步是对这个事件采取一些操作。MessageViewMediator 应该是合乎逻辑的选择。
MessageViewMediator.as
public class MessageViewMediator extends Mediator { [Inject] public var view:MessageView; override public function onRegister():void { addContextListener(HelloWorldMessageEvent.MESSAGE_DISPATCHED, handleMessage) } private function handleMessage(event:HelloWorldMessageEvent):void { view.text = event.message; } }
这样,我们就得到了 Hello World!这似乎是一个漫长的过程,但对您的重要应用程序来说,所付出的这些努力一定是值得的。在本系列的下一部分中,我们将探索模型和命令,随后的一篇文章将介绍服务。除了 Robotlegs 的核心之外,我们还会详细探讨目前可用的一些实用工具,以便利用 AS3-Signals 和模块化应用程序开发等工具。使用 Robotlegs 进行开发是一个令人愉悦的过程。
在我的博客中有一些关于各种 Robotlegs 主题的文章(互联网上也有其他许多此类文章)。John Lindquist 在他的博客上发表了一个 Hello World 截屏视频(观看他使用 FDT 非常有趣)。除此之外,这篇最佳实践文档已经证明了自身对于许多人的有用价值。您总是可以访问 Robotlegs 知识库来寻求帮助和支持。它拥有一支活跃的社区志愿者队伍,我本人也是其中的一员,我们非常乐于回答与 Robotlegs 有关的所有问题。有关这些主题的深入讨论以及更多内容,请参见面向 ActionScript 开发人员的 Robotlegs 指南。
敬请关注本系列的第 2 部分:模块。
原文链接: Introduction to Robotlegs – Part 1: Context and mediators
评论