Adobe Flash Builder 4 简体中文正式版 Windows版点击下载:http://g.csdn.net/5134151
Adobe Flash Builder 4 简体中文正式版 Mac版点击下载 :http://g.csdn.net/5134152
Adobe 在线课堂:http://adobev.csdn.net/zx/index.html
Adobe平台技术峰会课程视频:http://adobev.csdn.net/
这是InsideRIA 上关于R obotlegs AS3 入门介绍系列文章的第二部分。在第一部分,我们了解了何为Robotlegs AS3 ,并且简要介绍了Robotlegs 的Context (上下文)与Mediators (媒介)。本文将继续阐述这些概念,并介绍相关模型。
如果你错过了第一部分,请看Robotlegs AS3 入门介绍-- 第一部分:Context (上下文)与Mediators (媒介)(Part One: Context and Mediators )。
模型是什么?
模型类封装你的应用程序数据,提供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 的执行。
如果你不能等,在我的博客上(还有互联网的其它网站)有一些文章都是讨论的Robotlegs ,包括这个25 分钟的视频。John Lindquist 在他的博客上有个 Hello World 视频。此外,还有一个最佳操作文件,对很多人都大有裨益。你可以点击Robotlegs 知识库( Robotlegs Knowledge Base )获取帮助和支持。它拥有大批活跃的社区志愿者,包括我自己。志愿者们都很乐于回答有关Robotlegs 的任何问题。此外,我还参与编写了马上要发表的Flex 4 in Action ,这本书有22 页专门讨论 Robotlegs 。
本文译自:http://insideria.com/2010/06/an-introduction-to-robotlegs-a-1.html