robotlegs--【转】
现在,我们了解到有诸多的途径,可以用来在Actionscript 3的开发环境中构建框架,这是一个很好的迹象。开源社区是一个充满活力,富有创造性的地方,那些令开发工作变得更加简单的工具都是很好用的。经历了过去一年的发展,Robotlegs AS3得到了快速的推广和广泛的采用。目前,Robotlegs AS3已经广泛应用在主流媒体,创业公司以及各种规模的企业之中。
本文是InsideRIA社区中首发的关于Robotlegs 的系列文章之一。 在接下来的几个星期,将会有更多本系列的文章出炉,来详细介绍Robotlegs的核心概念以及一些更加高级的概念,包括第三方程序的使用以及使其相互作用的程序库。
什么是Robotlegs?
简单地来说,Robotlegs是一套机制,用来将您所创建的对象联接在一起。 比如说,对象A要传达信息给对象B。 然而,对象A不想或者说不需要知道对象B是否存在。 这样子两个对象之间如何进行信息交流呢? 一个简单的方法是通过事件(Events)来处理。 在Flash当中,我们有一个本地事件系统,用于促进这种类型的信息交流。 就像您平时应用Event(事件)那样。 显示列表中的对象通过事件进行信息沟通,事件冒泡使得远处的对象可以从其它显示对象中接收信息。 而对于那些没有在显示列表上的对象,该如何处理这一问题呢? 这时,像Robotlegs 这样的框架就可以很好地解决这个问题了。
Robotlegs的核心是一组模块化的工具集以及界面集,用来简化上述的信息沟通任务,减少重复的代码编写,以及管理应用程序中的依赖注入。除了这些核心工具集以外,Robotlegs还提供了一个比较规范的MVC+S(Model模型,View视图,Controller控件和Service服务)实现,来帮助您开展工作。如果您对PureMVC有经验的话,相信您也会很快学会使用Robotlegs MVC+S实现中的媒介(Mediators)和命令(Commands)。如果对PureMVC没有经验,也不必担心,在本课程中我们将会更深入地探讨这个问题。
本文将会通过一个简单的“Hello World”例子,对Robotlegs的功能进行概述。 当您看到这个例子的时候可能会说:“呃?我可以在单个的MXML文件中就能实现这个功能,何苦这么麻烦呢!” 对于这个微不足道的小例子来说,确实没必要这么麻烦,但是请注意,对于一个庞大的项目来说,这种结构的优越性将是无可比拟的。 总之,这是利用框架进行开发的好处。 相比起那些没有模式,不经实践,只是随意堆叠在一起的应用程序来说,这种方法可以让我们更有效,更快速地沟通概念以及理解代码库。
本文将不会对Robotlegs作过分详尽的探究,但我希望这些内容足以引起您的兴趣。 在文章的末尾,我粘贴了一些资源的地址,用以对Robotlegs作更进一步的探究。 接下来,我们来看一些代码的应用!
Robotlegs MVC+S 应用程序的基本结构
一个典型的Robotlegs MVC+S应用程序包含以下几个部分:
Context(上下文) - 所谓Context(上下文),实际上是一套自展机制,用来初始化Robotlegs所使用的依赖注入以及各种核心工具。
Mediators(媒介) - 所谓Mediators(媒介),是用来管理应用程序中的视图组件与应用程序中的其它对象之间的信息交流。
Commands(命令) - 所谓Commands(命令) ,代表的是应用程序所能执行的独立操作。 通常,Commands(命令)会作为对用户操作的反应,但命令的作用并不仅限于此。
Models(模型) - Models(模型)中保存着数据信息,并且表现出应用程序当前的状态。
Services(服务) - Services(服务),是应用程序与外界的接口。
我们现在来进一步地探讨一下Context(上下文)和Mediators(媒介),首先开始的是Context(上下文))。
Context处于应用程序的核心位置。 它提供了中央事件总线,使得您其它的应用程序对象之间可以进行信息交流。 比起应用程序初始化加载以及程序引导来说更优越的一点是,您在常规的开发工作中不需要去理会Context。 它会自发启动,执行自己的功能,并且,伴随者您开发出了程序的核心,它又会悄无声息地自动退出。 此外,Context并不是一个单例。 应用程序中可以存在自定义数量的Contexts,使得Robotlegs很适合用来开发模块化的应用程序。 我们不打算在这篇文章中深究模块化的应用程序,但是它将会是另一篇即将问世的文章的主题,因为模块化应用程序实在是一款极为强大的工具。 作为开始,我们先来看一下最基本的Context。
HelloWorldContext.as
import org.robotlegs.mvcs.Context
public class HelloWorldContext extends Context
{
override public function startup():void
{
//bootstrap here
}
}
Context当中的一些内容实际上让您重写了程序启动类函数。 当Context完全初始化以后,会调用startup()函数。 在程序后台,早在调用startup()函数之前,Context就已经创建了全部的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 property(Context视图属性)受限于应用程序本身。 context view(context 视图)是Context的根视图,结合view component mediation(视图组件媒介)为开发者提供自动化协助。 当在后面讨论到views(视图) 和 mediators(媒介)的关系时,我们会对context view(context 视图)进行简单的介绍。
以上是对Context的介绍。 正如前面所提到的,Context在应用程序生命周期中的作用时间非常短暂,但却处于决定性的地位。 准备好您的Context,我们现在可以加入一些视图组件并通过Robotlegs使他们相互间进行信息交流。 现在让我们来关注一下Mediators(媒介)以及它们与应用程序的视图组件之间的关系。
Mediators(媒介)处于视图组件和程序的其他部分之间。 简单地来说,Mediators会对事件进行监听。 当用户与视图组件进行交互或者视图组件经由其他一些方式进行更新时,这些视图组件都会分派出一些事件。 这些事件需要被程序的其它部分获取,并再次进行传递。 或许是用户点击了保存按钮,因此一些信息需要被传递到服务器中去。 而mediator正监听着这一事件,一旦这个事件发生,mediator就会收集适用的信息,并发送出一个事件,程序的其它部分可以根据这个事件来对数据执行某些特定的操作。 同样地,Mediator也会监听来自程序其它部分发送来的事件。 如果从服务器接收到一些被解析的数据,以及一个事件正被一个service类分派出来,那么Mediator就是您监听这个事件以及用新数据更新这个事件的视图组件的地方。 下面来看一下怎样接收一个Mediator。
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类之中,我们同时创建了一个特定的视图组件,使得依赖注入会依照这个视图组件来起作用。 如果我们的程序具有多个目的不同的TextAreas,那么这一点将是相当重要的。 通过这种方式来对类进行划分,我们可以很明确地定义类的意图,并且令到依赖注入工具可以有效地工作。 利用MessageView,我们以后还可以加入一些额外的功能。 拿这个例子来说,它仍然会是一个简单的TextArea,但您一定已经明白这个概念了。 现在,我们来看一下用于MessageView组件的mediator。
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识别那些需要执行注入操作的属性和类函数。 利用mediator的作用,当创建了一个mediator之后,对于注入操作来说总会有一个可用的mediated view(媒介视图)。 因此,您不需要对绘制注入操作的视图进行任何特殊的考虑。 当您为它的mediator绘制视图的时候,这一点已经被自动地考虑到了。 稍后,我们会具体地看一下上面所说的那一个特征。但现在我们先来看一下基本mediator的另外一个有趣的特征,这个特征体现在onRegister()函数中。
函数onRegister()是一个钩子,会在mediator完全初始化之后执行。 当注入操作完成,视图也准备好了,通常这里就是您为视图组件和程序添加事件监听器的地方。 一般来说,您会在每个您所创建的Robotlegs mediator中重写这个函数。
既然您已经得到了一个视图组件以及一个mediator,它们需要用Context来进行注册和定义映射。 这一步操作是通过MediatorMap来实现的。 正如其名,MediatorMap是一个将mediators映射到Context的视图组件中去的工具。 此外,默认状态下,MediatorMap 会监听ADDED_TO_STAGE事件以及REMOVED_FROM_STAGE事件的发生,以此判断是否会随着显示列表中视图组件的添加或者移除,来自动创建或者删除相应的mediators。 其实这并没有多大价值,对于画质精细的程序来说,显示对象的数量非常庞大(1000s),这个自动化媒介处理可能会引起一些性能问题。 当然,在一个典型的程序里,这还是非常方便的工具,并且也极少会引起性能问题。 以下代码展示了我们如何将MessageView 映射到HelloWorldContext的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>
现在,当在调试器中运行您的程序的时候,您将会看到控制台上显示“我已经注册了!” 祝贺您,您已经拥有一个功能完整的Robotlegs程序了。 但是,这个功能现在几乎是没有限制的,不过我们可以改变这一点。 让我们给这个程序添加一些除了自展以外的其它东西吧。下面,我们会添加一个称为HelloButton 的按钮,这仅仅是Spark Button类的一个扩展,同时会为HelloButton添加一个mediator,名为...... 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 以及它的mediator添加映射以后,您的Context启动函数看起来也会跟上面的一样。
override public function startup():void
{
mediatorMap.mapView(MessageView, MessageViewMediator);
mediatorMap.mapView(HelloButton, HelloButtonMediator);
}
同样,您可以往HelloWorld.mxml之中添加这个按钮,使其加入到显示列表中去。 您可能还希望把其它一些有趣的东西添加到HelloButton的label property(标签属性)之中,这些操作就留待您自己来完成了。
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>
到目前为止,我们有两个完全mediated(调解)的视图组件了,它们已经迫不及待想进行信息交流了。 我并不是一个会拒绝自己对象渴求的人,所以,就让它们交流去吧;首先需要创建一个用于信息交流的自定义事件。
HELLOWORLDMESSAGEEVENT.AS
public class HelloWorldMessageEvent extends Event
{
static const MESSAGE_DISPATCHED:String = "messageDispatched";
private var _message:String;
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的一些方面)!貌似这个认识过程稍微有点长,但是在您编写重要程序的时候,就会觉得付出的这些时间是值得的。在本系列文章的下一部分,我们将会探索一下models以及commands的应用,紧接着,我们还会学习一下services。除了Robotlegs的核心内容以外,我们还会在一些功能上深究,当我们使用AS3-Signals以及模块化程序开发等工具的时候,这些功能都派得上用场。应用Robotlegs来进行开发是相当令人兴奋的。我相当期待,在接下来几周的时间里能通过InsideRIA这个平台与大家分享我在这方面的一些热望。
如果您实在迫不及待了,我的博客里(以及许多其他网络资源中)有一些文章也是涉及到许多Robotlegs主题的。John Lindquist在他的博客上也放置了一段关于Hello World的截屏视频(观看他如何使用FDT本身来说也是非常有趣的)。此外,还有一个叫Best Practices(最佳实践)的文件,很多人认为这个文件对于学习Robotlegs是有很大帮助的。您还可以经常访问Robotlegs Knowledge Base(Robotlegs 知识库)寻求帮助和支持。那里有许多积极的社区志愿者,包括我自己在内,会很勤快地回答各种相关于Robotlegs 的疑问。除了以上所说的以外,我参与到一本名为Flex 4 in Action的图书编辑工作之中,这本书即将出版,当中有22页的篇幅是Robotlegs 的材料
模型是什么?
模型类封装你的应用程序数据,提供API 读取和操作数据。你的程序中的其它类会通过该API 请求模型。当模型中的数据更新后,模型会派遣事件,而你的程序中的其它类会对这些事件予以反应。模型适合于抓取域逻辑,如执行计算或其它操作。有个例子就是购物车。向购物车模型添加了一个条目之后,购物车内新的商品总量会再次统计。新的总量被存储在模型里,供程序中的其它类读取。将这个逻辑置入你的模型里,确保不会“散落”在整个程序里,你就可以确切地知道哪里去查看你的数据是怎么样,在何时被操作了。
除了控制数据的读取外,模型还将保持程序的状态(state) 。状态(State ),就数据模型这个意义来说,和Flex State 并非同一个概念,这与控制程序的外观有关。当然,它们也是相关的,但是有了这个模型,就要考虑一系列对象。你想要跟踪选定的目标,所以数据模型有一个选择属性,随时根据选定的条目更新。程序的其它领域现在可以读取这个属性,查看哪个条目被选定,然后做出相应的反应。
模型可编写成便携式的。就如同你要牢记Services (服务)一样,在开发模型时,你也要记住这点。有许多常见的数据组,可以轻松在一个程序和下一个之间传输。譬如,UserLoginModel 或者ShoppingCartModel 。让模型具有编写性,也不过是多花费些时间和精力,但是也就像为每个项目重复编写一次代码差不多。
模型的确值得你更多的注意力。它是你的程序的核心。大家总是惊叹于这些视觉组件。但是作为一个开发人员,你知道,数据才是真正最为重要的。这就是我们的任务,作为开发人员的任务:创建数据,精确传送到那些美丽的界面。这就是为什么在模型中隔离域逻辑非常重要。通过隔离,你就可以更轻松地定位,更新和维护数据了。对模型进行了基本的定义后,我们来看看一个小例子。
看看代码!
这个纯粹的 AS3 程序利用的是Keith Peters 出色的Minimal Comps 库。不要担心,如果你喜欢Flex ( 你怎么会不喜欢呢?) , 下一个例子就是一个Flex 程序。不过有了Minimal Comps 库,就可以很快地使用AS3 举出例子;而Robotlegs 和Flex 一样,使用起来很轻松。所以,我们来看看程序的具体内容吧。
上面是一个我们将要检查的程序。这是一个简单的作者列表,选定的作者会有一段引言展示。这是一个归档的Flash Builder 项目 (zip 168k), 包括Robotlegs 1.1 和MinimalComps SWC 文件。
SIMPLELISTEXAMPLE.AS
public class SimpleListExample extends Sprite
{
public function SimpleListExample()
{
stage. align = StageAlign.TOP_LEFT;
stage. scaleMode = StageScaleMode.NO_SCALE;
}
}
这是程序的主要文件,进入点。这是个简单的Sprite 。是Robotlegs 程序。所以我们需要做的第一件事就是创建一个Context: (上下文)。
SIMPLELISTEXAMPLECONTEXT.AS
public class SimpleListExampleContext extends Context
{
public function SimpleListExampleContext(contextView:DisplayObjectContainer)
{
super (contextView);
}
override public function startup(): void
{
injector.mapSingleton(AuthorModel);
mediatorMap.mapView(ListView, ListViewMediator);
mediatorMap.mapView(QuoteTextArea, QuoteTextAreaMediator);
mediatorMap.mapView(SimpleListExample, ApplicationMediator);
}
}
在Flex 程序中,通常会完全省略构造函数,因为在MXML 声明内,设定了contextView 。在Actionscript 程序中,你将contextView 传入构造函数中,这样可以立即设定。现在,我们在主application view 中创建一个SimpleListExampleContext 的实例。
SIMPLELISTEXAMPLE.AS
public class SimpleListExample extends Sprite
{
public var context:SimpleListExampleContext;
public function SimpleListExample()
{
stage. align = StageAlign.TOP_LEFT;
stage. scaleMode = StageScaleMode.NO_SCALE;
context = new SimpleListExampleContext( this );
}
}
你应该注意到上下文变量。必须有一个上下文的参考,这点很重要。如果你只是在构造函数里创建一个SimpleListExampleContext 的新实例,而不将它放入变量,那它就是一个收集在Flash Players 上的无用“垃圾”。这会导致一些麻烦,需要花费时间认真处理!有了Flex, 你只需在MXML (紧跟参照,而无需变量)中声明上下文,除非你在Script tag 中创建了上下文。它将要求一个参照,正如上面给出的Actionscript 范例。
程序有两个视图。可以选定一个名称表,而文本区域会显示列表中选定条目的quote 。这些视图为:
l ListView
l QuoteTextArea
命名方案倒还有创意。这两个视图就是基础Minimal Comps 类的次类。它们是:
LISTVIEW.AS
public class ListView extends List
{
public function ListView(parent:DisplayObjectContainer)
{
super (parent);
}
}
QUOTETEXTAREA.AS
public class QuoteTextArea extends TextArea
{
public function QuoteTextArea(parent:DisplayObjectContainer)
{
super (parent);
}
}
这个程序只有其中的一个。如果我们只是使用基础列表和TextArea 类,功能性就非常强。为什么都是使用次类?这是一个好习惯,尤其当处理依赖注入时。如果我们知道,我们将要协调一个视图类,将它加入次类,然后根据其目的进行命名。这么做时,很容易就隔离开,以进行协调。
现在,我们需要做的就是,将这两个视图作为主程序的子程序放入stage 上。在
Actionscript 程序中,我倾向于将createChildren 方法放在主视图上,然后从mediator 中调用。下面是才采用createChildren 方法的主视图。
SIMPLELISTEXAMPLE.AS
public class SimpleListExample extends Sprite
{
private var hbox:HBox;
private var list :ListView;
private var quoteText:QuoteTextArea;
public var context:SimpleListExampleContext;
public function SimpleListExample()
{
stage. align = StageAlign.TOP_LEFT;
stage. scaleMode = StageScaleMode.NO_SCALE;
context = new SimpleListExampleContext( this );
}
/**
* Called from ApplicationMediator's onRegister()
*/
public function createChildren(): void
{
hbox = new HBox( this ,0,0);
addChild(hbox);
list = new ListView(hbox);
list .alternateRows = true ;
quoteText = new QuoteTextArea(hbox);
quoteText.editable = false ;
quoteText. selectable = false ;
}
}
你可能会想,为什么不直接在构造函数中添加视图?我倾向于在添加子程序之前,启动Robotlegs 。从ApplicationMediator 中调用createChildren() ,我们可以百分百地确定所有的初始应用bootstrapping 已经完成,准备完毕。下面是ApplicationMediator:
APPLICATIONMEDIATOR.AS
public class ApplicationMediator extends Mediator
{
[Inject]
public var view:SimpleListExample;
override public function onRegister(): void
{
view.createChildren();
}
}
在本例中,mediator 只有一个任务,但是在你的程序中,协调视图(contextView )是个轻便的机制。Mediator被创建之后,需要被映射。这一步很简单,也容易被遗漏,但是当你遇到bug ,而你的mediator 不予反应时,你就会想起来。很有可能发生的情况就是;它没有被映射,或者它的视图组件不是ADDED_TO_STAGE 。在你的上下文中,启动的方法如下所示:
SIMPLELISTEXAMPLECONTEXT.AS
override public function startup(): void
{
mediatorMap.mapView(SimpleListExample, ApplicationMediator);
}
当你现在运行程序时,你应该看到一个空列表和文本区域。现在,我们需要获取一些数据视图。我们将在模型类里放入一些静态数据,而我们的视图组件的mediator 可以读取。下面是基本的模型:
AUTHORMODEL.AS
public class AuthorModel extends Actor
{
private var _list: Array ;
public function get list (): Array
{
if (!_list)
initializeList();
return _list;
}
protected function initializeList(): void
{
var twain:Author = new Author(" Twain ");
var poe:Author = new Author(" Poe ");
var plato:Author = new Author(" Plato ");
var fowler:Author = new Author(" Fowler ");
twain.quote = " Why, I have known clergymen, good men, kind-hearted, liberal, sincere " +
" , and all that, who did not know the meaning of a 'flush.' It is enough " +
" to make one ashamed of one's species. ";
fowler.quote = " Any fool can write code that a computer can understand. " +
" Good programmers write code that humans can understand. ";
poe.quote = " Deep into that darkness peering, long I stood there, wondering, " +
" fearing, doubting, dreaming dreams no mortal ever dared to dream before. ";
plato.quote = " All things will be produced in superior quantity and quality, and with greater ease, " +
" when each man works at a single occupation, in accordance with his natural gifts, " +
" and at the right moment, without meddling with anything else. ";
_list = [twain,fowler,poe,plato];
}
}
你马上会注意到AuthorModel 拓展 Actor 。Actor 是一个配有MVCS 的便捷类。它提供了恰当的代码,用于注入用来在上下文中沟通的IEventDispatcher ,及通过该调度程序发送时间的dispatch() 方法。不一定要拓展Actor, 但是如果不拓展的话,你就需要亲自提供IEventDispatcher 注入点。Robotlegs MVC+S 中的模型是纯粹概念性的。没有模型类可拓展,命名规则不过是用来表述类的意图。
模型现在提供一个我欣赏的作者列表,并附有每位作者的一段引言。从下面可以看出,作者类是一个可以保存这些属性的简单的价值对象:
AUTHOR.AS
public class Author
{
public var name : String ;
public var quote: String ;
public function Author( name : String )
{
this . name = name ;
}
/**
* Minimal comps took issue with toString();
* @return
*
*/
public function get label (): String
{
return name ;
}
}
当模型中充满数据时,我们需要向列表传递数据,以供展示。第一步就是,映射模型,进行注入。第二步就是协调ListView ,将数据从模型转移到列表。你的上下文的startup() 方法,在模型映射之后,会如下所示;
SIMPLELISTEXAMPLECONTEXT.AS
override public function startup(): void
{
injector.mapSingleton(AuthorModel);
mediatorMap.mapView(SimpleListExample, ApplicationMediator);
}
为了映射模型,或者其它你想要诸如的类,你将使用上下文的注入器。注入器有几个方法用于映射类和值。mapSingleton() 方法是常用来为注入而映射简单类的方法。应该注意到,在该上下文中,“singleton ”不是指的设计模式中严格意义上的Singleton 。在本例中,mapSingleton() 意味着只要有要求,注入器将创建,并提供AuthorModel 的单一实例。如果你想要的话,你可以有任意个AuthorModel 实例,但是注入器只会创建,并注入你使用上述映射要求的那一个。还有其它几个方法可使用注入器进行映射,我强烈推荐您读一读Till Schneideriet 关于SwiftSuspenders Injector 的文章。在缺省状态下,这就是Robotlegs 使用的。Robotlegs最佳实践(Robotlegs Best Practices )也讨论了使用注入器。此外,我们还将讨论其它注入映射选择。
有了用于注入的,已被映射的模型,我们就可以进一步将作者放入他们的列表。为此,我们将协调ListView ,将AuthorModel 注入 mediator 中。下面是ListView 的mediator :
LISTVIEWMEDIATOR.AS
public class ListViewMediator extends Mediator
{
[Inject]
public var view:ListView;
[Inject]
public var authorModel:AuthorModel;
override public function onRegister(): void
{
view.items = authorModel. list ;
}
}
在协调的视图组件 [Inject] 标签外,我们没有看[Inject] 标签的使用,也没有讨论Robotlegs 如何提供读取你的类的依赖注入方法。在ListViewMediator 中,你会看到视图被注入。除了视图外,还有一个公共属性,属于类型AuthorModel 的AuthorModel ,标识有[Inject] 标签。当你在公共属性上放置[Inject] 标签,Robotlegs 创建类时,它将根据你映射的规则“注入”该属性。注意,如果你没有为注入提供规则,你会收到一个运行时间错误,将你指向问题所在。如果这种情况发生,确保捡查映射。在本例中,我们还使用了mapSingleton() ,以让AuthorModel 为注入做好准备。
当mediator 被完整地构架,并且提供注入,使用运行的mediator 的onRegister() 方法,我们读取模型,并且将列表属性赋值到视图的条目属性。真棒!我们现在应该看看列表中的条目。
... 好...
... 真棒!...
首先,我们需要映射mediator :
SIMPLELISTEXAMPLECONTEXT.AS
override public function startup(): void
{
injector.mapSingleton(AuthorModel);
mediatorMap.mapView(ListView, ListViewMediator);
mediatorMap.mapView(SimpleListExample, ApplicationMediator);
}
这就对了。ListView 被协调。记住映射的次序。ListView mediator 映射在SimpleListExample ( 主图) 映射之上。SimpleListExample 是主图,正如 contextView 。当contextView 的类被映射时,它的处理略有不同。contextView 马上被协调,而不是监听ADDED_TO_STAGE 事件的发生。鉴于contextView 已经出现,我们不能坚持监听可能不会发生的事件。因为我们想要ListView 被添加时,进行协调,我们要确保它在ApplicationMediators onRegister() 有机会被调用前被映射。如果你再次调用,就是在ApplicationMediator's onRegister() 这里,我们将要通知主视图添加子集,包括 ListView 。
所以,当ListView 被填充满数据,你现在可以从列表中的作者列表中进行选择了。非常激动吧?稍微控制下自己吧。不过还有步骤要继续。现在,我们想要用一些文本填充QuoteTextArea ,最好是选定作者的引言。为此,我们将要向AuthorModel 做一些添加,以跟综选定的作者。当作者在ListView 内选定,ListViewMediator 将更新AuthorModel 。AuthorModel 然后发送事件,通知任何可能任何可能正在监听的人,作者被选定。我们还需要为QuoteTextArea 创建一个mediator ,这样就可以监听该事件,并且根据选定作者的引言予以更新。我们知道我们会需要该事件,所以首先;
SELECTEDAUTHOREVENT.AS
public class SelectedAuthorEvent extends Event
{
public static const SELECTED: String = " authorSelected ";
private var _author:Author;
public function get author():Author
{
return _author;
}
public function SelectedAuthorEvent( type : String , author:Author = null , bubbles: Boolean = false, cancelable: Boolean = false )
{
super ( type , bubbles, cancelable);
_author = author;
}
override public function clone():Event
{
return new SelectedAuthorEvent( type , author, bubbles, cancelable)
}
}
该事件并不引人注意。它就是个典型的定制事件,有一个常量,SELECTED ,用于可选择的作者参数。鉴于我们已经有了该事件,我们需要更新AuthorModel ,跟综选定的作者,当出现更改时,通知程序。
AUTHORMODEL.AS
private var _selected:Author;
public function get selected():Author
{
return _selected;
}
public function set selected(value:Author): void
{
_selected = value;
dispatch( new SelectedAuthorEvent(SelectedAuthorEvent.SELECTED, selected));
}
有了添加到AuthorModel 的代码(如上所示),我们能够有效地跟综现在选定的作者。为了在该模型上设定该属性,我们将更新ListViewMediator ,为ListView 选定的事件进行监听,并适时更新模型。
LISTVIEWMEDIATOR.AS
override public function onRegister(): void
{
view.items = authorModel. list ;
addViewListener(Event.SELECT, handleSelected)
}
private function handleSelected(event:Event): void
{
authorModel.selected = view.selectedItem as Author;
}
在ListViewMediator's onRegister() 里面,我们将使用addViewListener() 方法,在视图上添加一个事件监听器,使用handleSelected 方法处理。现在,当列表中的条目被选定后,事件处理器方法会读取authorModel 选定的属性,并且使用选定的条目进行更新。设定该值,然后AuthorModel 会派遣事件,通知程序其它部分已经发生的内容。
“为什么不在这里派遣选定事件,然后执行呢?”
你也可以这样,如果你的程序很简单的话,这样做也是一个恰当的方法。但是我没有遇到很多这么简单的程序,这就是如何使用Model 的一个范例。因此,我们需要做的工作还很多。鉴于我们已经开始了这个操作,我们还需要将该文本添加到QuoteTextArea, 这样可以进行协调。
QUOTETEXTAREAMEDIATOR.AS
public class QuoteTextAreaMediator extends Mediator
{
[Inject]
public var view:QuoteTextArea;
override public function onRegister(): void
{
addContextListener(SelectedAuthorEvent.SELECTED, handleSelectedAuthorChanged)
}
private function handleSelectedAuthorChanged(event:SelectedAuthorEvent): void
{
var author:Author = event.author;
view. text = author.quote;
}
}
就是这样了。QuoteTextArea 现在与程序其它部分连接起来。在它的onRegister 里面,我们将使用addContextListener() 为SelectedAuthorEvent.SELECTED 进行监听,使用with handleSelectedAuthorChanged 进行处理。
handleSelectedAuthorChanged 方法从事件中提取信息,更新视图的文本属性(使用选定作者的引言)。此后,我们获得被映射的mediator ,我们将实现本范例的功能性的深度。
SIMPLELISTEXAMPLECONTEXT.AS
override public function startup(): void
{
injector.mapSingleton(AuthorModel);
mediatorMap.mapView(ListView, ListViewMediator);
mediatorMap.mapView(QuoteTextArea, QuoteTextAreaMediator);
mediatorMap.mapView(SimpleListExample, ApplicationMediator);
}
有了这个,你就有了范例,该范例包括了在Robotlegs 程序中使用Model 的基本条件。作为数据的护卫,模型在你的程序中发挥了非常重要的作用。利用模型作为数据的读取点,你在数据存储和操作的位置隔离。当你坐下来解决一个问题时,你知道在哪里找关于数据的内容及随后程序状态的显示。如果你的数据散落在程序中,就很难快速隔离问题点,或者给程序添加功能。
一篇博文简要介绍了模型。我强烈推荐对MVC 中的M 进行研究。在下一个范例中,我们来看看服务,并且看看在Robotlegs 程序中如何使用它们。在服务之后,我们会查看Commands ,以在讨论下一步课题之前,完成Robotlegs MVC+S 的执行。