您的位置:首页 > 其它

Robotlegs AS3入门介绍 第二部分:Models(模型)

2013-02-27 15:04 453 查看
原文地址: http://insideria.com/2010/06/an-introduction-to-robotlegs-a-1.html

这里是在介绍第二部分关于Robotlegs AS3系列里面的insideRIA。 在系列里的第一部分,我们学习了什么是Robotlegs S3,并且针对Robotlegs内容和仲裁课有了有了简短的“HelloWorld”介绍。这篇文章将会扩大这些概念并且介绍模型。



如果你错过了,这里是第一部分: 内容和mediators类


什么是模型?

模型类将您的应用程序数据封装起来并且提供了一个API用以访问和控制那个数据。 在您应用程序里的其他类将通过这个API请求模型。当这个模型上的数据升级后,模型会分离其他的events使得在应用程序里的其他类可以反映 模型有利于捕获域的逻辑,比如计算能力火其他操控 购物车可以作为这里的一个例子 当一个项目加入到这个购物车模型中,在车里的全部新的项目会被重新计算。 然后会在应用程序里存储在模型上用以其他类的通过。 通过放置这样的逻辑在你的类里,你可以保证它不会在整个应用程序里被分散并且会确切知道你的数据怎样并且何时在被操控着。

除了控制数据的通过,模型还保持了你应用程序的当前状态。 State状态,在数据模型里和Flex State状态并不是一样的,Flex State和控制应用程序里的appearance外观是有联系的。 他们是有联系的的,但是仅在一个模型里,被考虑为物体的一个列表。 您想跟踪哪个物体被选择了,所以数据模型里有一个选择属性,当您选择这个项目,他会得以升级。 这样,应用程序中的其它部分便能通过访问这一属性来获知哪个条目被选中,并作出相应的应答。

Models可以编写为可移植的。 在您开发model的时候,这一点和Service(服务)一样,都是要牢记于心的。 有很多通用的数据集可以很轻易地在两个应用程序之间进行传输。 试想一下,比如一个UserLoginModel或者ShoppingCartModel。 尽管要实现可移植的话确实需要花一些心思,但也仅仅是为每一个项目编写重复相同的代码而已。

Robotlegs中的model是值得重点关注的。 因为它是应用程序的核心。 尽管用户都是通过可视组件来获取信息,但是作为开发者,您应该知道组件背后的数据才是程序的主体。 我们作为开发者的工作,就是处理这些数据,并且将其准确地传递到那些美观的界面条目中去。 这就是model里面分离的域逻辑之所以如此重要的原因。 通过这种分离,您可以更容易地进行定位,更新和维护。 掌握了这些关于model的基本定义之后,我们来看一个简单的应用示例。


代码示例

这个纯粹的AS3应用程序利用了Keith Peters's awesome Minimal Comps library。 不要担心,如果您热衷于Flex(相信您一定是这样的),那么下一个例子就会是Flex应用程序,但是Minimal Comps使得利用AS3来创建快速示例会更加简单。并且利用Actionscript应用程序的方式来使用Robotlegs与利用Flex的方式同样
简单 。所以,让我们来看看这个应用程序,并分段验证它的功能。

SimpleListExample.zip

我们将会验证上述的应用程序。这是一个简单的作者列表,右边显示了关于所选择作者的评论。这里是一个存档的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应用程序中,我们通常会完全忽略结构体(constructor),因为MXML声明里已经设置了contextView。 在Actionscript应用程序里,您会将ContextView传递到结构体之中,以便其能够立即被设置。 现在,让我们在主应用程序里创建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);
    }
}

您应该会注意到context这个变量。 请记住,这里对context的引用是非常重要。 如果您仅仅是简单地在构造函数里创建一个新的SimpleListExampleContext实例,而没有将它设置到变量里的话,那么Flash Players会将它回收。 这个问题往往会使您陷入深深的迷惑之中。 使用Flex的话,除非这个context是在Script标签里创建,否则您只需要在MXML中声明context即可,MXML会一直保持对它的引 用,而不需要用到变量。 这将需要一个引用,正如以上的Actionscript例子中那样。

这个应用程序将包括两个视图。 一个是用于选择的名字列表,另一个是文本区域,它显示了列表里选择条目所对应的引文。 这两个视图分别称为:
ListView
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);
    }
}

这两个子类在该应用程序里的数目都是一个。 如果我们仅仅应用基本的List和TextArea类的话,将会更具功能化。 那为何还要费心用到这些子类呢? 因为习惯使然,并且在处理依赖注入的问题时,这将是一个好的习惯。 如果我们要传递一个视图类,只需要将其设为子类,并根据视图的作用来命名即可。 当我们采用这种方式时,可以有效而简单地将其与信息传递分离开来。

我们现在需要做的是,把这两个视图放到舞台上,并入到主应用程序当中。 在Actionscript 应用程序中,我偏好的做法是,在主视图中设置一个creatChildren方法,并且从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);
      }
 
     /**
      * 从ApplicationMediator的 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里调用creatChildren(),我们可以百分之百地确定所有主应用程序的引导都已经运行完毕,然后 我们才可以开始下一步的工作。 以下是用到的ApplicationMediator:


ApplicationMediator.as

public class ApplicationMediator extends Mediator
{
   [Inject]
   public var view:SimpleListExample;
 
override public function onRegister():void
   {
      view.createChildren();
    }
}

Mediator在这个示例中只具有这个职责,但是在你的应用程序里,传递contextView视图是一个非常方便的操作。 创建了mediator之后,它还需要定义映射。 这是一个很容易被忽略的步骤,但当您调试程序,并且mediator没有响应的时候,应该很自然地想到这一点。 导致这种情况的原因是mediator没有被映射,或者是没有添加视图组件。 在context中的启动方法大致如下所示:


SimpleListExampleContext.as

override public function startup():void
{  
     mediatorMap.mapView(SimpleListExample, ApplicationMediator);
}

现在,当您运行这个应用程序的时候,应该就可以看见一个空的列表和文本空间了。 然后我们需要为视图添加一些数据。 我们会将一些静态数据放置在一个Model类之中,并且视图组件中的mediators能够访问这个Model类。 以下是一个基本的model:


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(用在context中进行通信)的合适的代码,还提供了一个dispatch()方法,用作事件的发 送。它并非严格地需要继承Actor,但是如果您不是这样做的话,就要自己提供一个IEventDispatcher 的注入点。Robotlegs MVC+S中的Model纯粹是概念性的。 没有Model类需要继承,并且命名规则仅仅是根据类的用途。

如我所愿,这个model现在提供了作者的列表,并且包括了对每个作者的评论。 Author(作者)类仅仅是一个值对象,用作保持如下所示的属性:


Author.as

public class Author
{
   public var name:String;
   public var quote:String;
 
   public function Author(name:String)
   {
      this.name = name;
    }
 
    /**
     * Minimal comps对toString()这个方法存在争议;
     * @return
     *
     */
   public function get label():String
   {
        return name;
    }
}

当model填充好数据之后,我们需要将这些数据传递到用以显示的列表当中。第一步是映射model,用于注入(injection)。 第二步是要传递Listview,并且将数据从model传递到列表。加入了model的映射之后,context中的startup()方法大致如下所 示:


SimpleListExampleContext.as

override public function startup():void
{
   injector.mapSingleton(AuthorModel);
  
   mediatorMap.mapView(SimpleListExample, ApplicationMediator);
}

为了映射这个model,或者映射其它希望注入的类,您可以使用context的注入器(injector)。这个注入器有多个用于映射类和值的方 法。其中,mapSingleton()是映射简单类时普遍用到的方法。应该注意的是,这里提到的singleton术语与设计样式中严格意义的 Singleton是不一样的。在这个情况下,mapSingleton()意味着注入器将会创建并且提供一个AuthorModel实例,只要它接收到 请求。您想创建多少个AuthorModel实例都可以,但是注入器只会为一个实例提供映射。用于注入器映射的方法还有许多,我强烈推荐阅读一下关于
SwiftSuspender injector的Till Schneideriet's documentation文档。默认情况下,这就是Robotlegs所使用的。Robotlegs Best Practices documentation中也讨论了注入器的使用。除了这个文档以外,我们也会在以后的文章中讨论其它注入映射方法。

当model完成了注入映射(mapped for injection)之后,我们向目标实现(将作者归入到列表之中)又迈进了一步。 为了达到这一点,我们将要传递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创建的时候,它会基于您的映射规则来inject(注入)该属性。 请注意,如果您没有为注入提供一个规则,则会接收到一个runtime错误提示,这将指导您找出错误所在。如果发生了这种情况,请检查一下您的映射设置。
在这个示例中,我们已经使用mapSingleton()来为AuthorModel的注入做好准备了。 

利用mediator中的onRegister()方法(当mediator被成功地构造并且提供了注入之后,这个方法便会运行),我们可以访问model,并且将列表属性分派到视图的条目属性当中。很好!我们现在可以看到列表中的条目了。

…不错……

…哦,对了!…

我们首先需要映射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是主视 图。当contextViewer被映射的时候,会稍微有点不同。contextView 会被马上传递,而不是等待ADDED_TO_STAGE事件发生后才被传递。因为contextView已经在舞台上了,我们不能等待一个可能永远不会发 生的事件。由于我们希望ListView在添加到舞台的同时被传递,因此我们需要确保在ApplicationMediators
的onRegister()方法有机会被调用之前,便完成映射。如果您再次调用,ApplicationMediator的onRegister()会让 主视图添加它的子代(children),包括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之后,我们现在可以有效地跟踪被选择的Author了。 为了在模型上设置这个属性,我们将升级ListViewMediator,以便监听一个ListView的选择事件,并且当这个事件发生时更新model:


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的onRegister()里,我们将使用addViewListener()方法来添加一个事件侦听器到视图 中,用来监听Event.SELECT事件,这个事件将会由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事件,并且适用handleSelectedAuthorChanged来对其进行处理。



handleSelectAuthorChanged方法将从e事件里提取出信息,并且以所选择的作者的评论来更新视图的文本属性。 在映射了那个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使用基础的例子。 作为您的数据管理者,Model在应用程序里扮演了极其重要的角色。 通过使用model作为数据的访问点,您可以将数据的存放点和操作点分离开来。 当您解决了这个问题之后,您就可以知道在哪里能够找到应用程序数据状态以及相关的信息了。 如果您的数据在应用程序里是非常分散的分布方式,将变成一个大杂烩,使您难以快速地找到出现问题的数据点,或者难以添加功能组件到应用程序之中。

在这个blog里分享了对model的一些简要的介绍。 我强烈推荐将其分割成MVC中M的一些研究。下一个例子中我们将会介绍Services,并且看一下在Robotlegs应用程序中是如何使用它们的。 在介绍了Services之后,且在进行更深入的探讨之前,我们将会介绍一些命令,这些命令式用来完成Robotlegs的

如果您实在是迫不及待,我的博客中(以及许多其他网络资源中)还有一些文章是涉及到Robotlegs的许多相关主题的(其中包括一个25分钟的视频)。John Lindquist在他的博客上也放置了一段关于Hello
World的截屏视频。此外,还有一个名为Best Practices(最佳实践)的文件,很多人认为这个文件对于学习Robotlegs是有很大帮助的。您还可以经常访问一下Robotlegs
Knowledge Base(Robotlegs 知识库), 以寻求帮助和支持。那里有许多积极的社区志愿者,包括我自己在内,会很乐意回答各种相关于Robotlegs 的疑问。除了以上所说的以外,我参与到一本名为Flex 4 in Action的图书编辑工作之中,这本书即将面世,当中有22页的篇幅是Robotlegs 的材料。

转载:http://www.ebibi.com/i/experience/2011/0214/2939.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: