Robotlegs AS3入门介绍--第三部分:Services(服务)

原文地址:  http://insideria.com/2010/06/an-introduction-to-robotlegs-a-2.html

本文是insideRIA上发表的介绍Robotles AS3系列文章的第三部分。该系列文章的第一部分向我们介绍了什么是Robotles AS3,并通过一个“HelloWorld”例子对Context(上下文)与Mediators(媒介)类进行了简要概述。在第二部分中,我们探索了 Models(模块)的相关内容。而本文将继续拓展前两部分中的内容,并对Services(服务)进行介绍。

如果您没有学习前两部分的内容,我建议您先回头阅读一下:

  • 第一部分:Context 和 Mediators
  • 第二部分:Models

Service是什么?

这个网站的名称是InsideRIA。 所谓Service是指您的应用程序与RIA中的I之间的连接。Services为您的应用程序提供了一个API来访问网站服务,数据源,以及其他一些应 用程序。 您所访问的资源可能是Twitter API,公司/客户端的web服务器,文件系统,或者是应用程序之外的带有其他数据源的主机。

一个Service可以远程调用以及解析出结果。 结果解析出来以后,service将会通知其余的应用程序:数据已经准备好,并/或以这些结果为标准来更新Model。 我们希望外部数据在我们的应用程序中存留的时间尽可能地短。 一旦得到我们想要的结果,这些数据就会转化成一种专用于应用域的格式。 这些外部数据有可能是XML,JSON或者源于AMF的输入数据。 当我们在一个可控的环境下工作时,那就意味着服务器端的中层是可控的,有时我们不需要去解析所有的数据,因为返回的对象已经是应用域的一部分了。 在许多情况下,我们会在连接第三方服务器的同时接收外来数据。 而这些数据正是我们需要解析的。

让我们通过实例来进一步加深理解

在了解相关的定义之后,我们来看一下在Robotlegs AS3 中的Service是如何实现其功能的。 以下的示例是通过使用基本 Twitter Search API来实现的。

上图概述了service是如何实现功能的。 它先接受请求,然后向服务器索取数据,解析结果,最后通知其他应用程序。 而这些请求通常是由Mediators和Commands发出的。 在这个例子中,我们将会在Mediator中发出请求,以使过程变得简单。 在下一篇文章中,我们将会围绕 Commands是如何完成其获得服务的强大功能这个问题进行阐述。 让我们先来看下它的工具集,并从基础学起。

下载 ServiceExample.zip

service与 TweetDeck并不相同,但是当访问Twitter API时,在使用的简易程度上与 TweetDeck不相上下。这个小工具会访问Twitter Search API,并返回“robotlegs”的实时结果。 开始的时候我们会尽可能简单地阐述问题,今后有必要的话我们将会加大复杂的程度。 我们来看一下整合的程序片段:

ServiceExample.mxml

   
   
   
   
  1. <?xml version= "1.0" ?>
  2. <s :Application xmlns :fx= "http://ns.adobe.com/mxml/2009"
  3. xmlns :s= "library://ns.adobe.com/flex/spark"
  4.                xmlns :service= "robotlegs.examples.service.*"
  5. xmlns :mx= "library://ns.adobe.com/flex/mx"
  6.                xmlns :view= "robotlegs.examples.service.view.*"
  7.                width= "300"
  8.                height= "300" >
  9.     <fx :Declarations >
  10. <service :ServiceExampleContext contextView= "{this}" />
  11.      </fx :Declarations >
  12.     <view :SearchResultsView />
  13. </s :Application >

这个是主程序视图。在Declarations标签中,您会注意到我已经做好一个可用的ServiceExampleContext实例。如果您没 有了解过第一和第二部分的内容,我建议您现在就学习一下。前两章的内容包括了上下文(Context)的创建和初始化。除了 ServiceExampleContext以外,该应用还添加了SearchResultView的功能。这个示例里只显示了一个视图。请参考一下这里 的上下文(Context):

ServiceExampleContext.as

   
   
   
   
  1. public class ServiceExampleContext extends Context
  2. {
  3. override public function startup ( ) : void
  4.      {
  5.           mediatorMap.mapView (SearchResultsView, SearchResultsViewMediator );
  6.       }
  7. }

我们在 SearchExampleContext中为SearchResultsView设计了一个媒介。 SearchResultsView 是 Spark List 组件的一个子类。

SearchResultsView.mxml

   
   
   
   
  1. <?xml version= "1.0" ?>
  2. <s :List
  3. xmlns :fx= "http://ns.adobe.com/mxml/2009"
  4. xmlns :s= "library://ns.adobe.com/flex/spark"
  5. xmlns :mx= "library://ns.adobe.com/flex/mx"
  6. itemRenderer= "robotlegs.examples.service.view.renderers.SearchResultItemRenderer"
  7.          height= "100%" width= "300" >
  8. <s :layout ><s :VerticalLayout gap= "0" paddingBottom= "10" paddingRight= "10" paddingLeft= "10" paddingTop= "10" /></s :layout >
  9. </s :List >

SearchResultsView 需要一个能与其他应用程序进行数据传递的媒介:

SearchResultsViewMediator.as

   
   
   
   
  1. public class SearchResultsViewMediator extends Mediator
  2. {
  3.      [Inject ]
  4.      public var view :SearchResultsView;
  5.  
  6. override public function onRegister ( ) : void
  7.      {
  8.           //Make the magic!(缔造奇迹!)
  9.       }
  10. }

既然 SearchResultsView已经与其他组件连接好,那么接下来我们将开始学习能够连接远程 Twitter server 并给我们返回一些数据的服务程序。 如果您仔细研究SearchResultsView ,将会发现我们也已经给它添加了 ItemRenderer。 我们会粗略地介绍一下。 首先,我们来看一下连接 Twitter 并获得数据的基本服务类:

TwitterSearchService.as

   
   
   
   
  1. public class TwitterSearchService extends Actor implements ISearchService
  2. {
  3.      private var loader : URLLoader;
  4.  
  5.       public function TwitterSearchService ( )
  6.      {
  7. loader = new URLLoader ( );
  8.       }
  9.  
  10.       private const TWITTER_SEARCH_ENDPOINT : String = "http://search.twitter.com/search.json";
  11.      public function getResults (forQuery : String = "robotlegs" ) : void
  12.      {
  13.           var urlRequest : URLRequest = new URLRequest (TWITTER_SEARCH_ENDPOINT );
  14.           var params : URLVariables = new URLVariables ( );
  15.  
  16.           params.q = HTMLStringTools.htmlEscape (forQuery );
  17.           params.rpp = 100;
  18. urlRequest. data = params;
  19.  
  20.           addLoaderListeners ( );
  21.  
  22. loader. load (urlRequest );
  23.       }
  24.  
  25.       private function handleError (event : SecurityErrorEvent ) : void
  26.      {
  27.           removeLoaderListeners ( );
  28.       }
  29.  
  30.       private function handleLoadComplete (event : Event ) : void
  31.      {
  32.           removeLoaderListeners ( );
  33.       }
  34.  
  35.       private function addLoaderListeners ( ) : void
  36.      {
  37.           loader. addEventListener ( IOErrorEvent. IO_ERROR, handleError );
  38.           loader. addEventListener ( SecurityErrorEvent. SECURITY_ERROR, handleError );
  39.           loader. addEventListener ( Event. COMPLETE, handleLoadComplete );
  40.       }
  41.  
  42.       private function removeLoaderListeners ( ) : void
  43.      {
  44.           loader. removeEventListener ( IOErrorEvent. IO_ERROR, handleError );
  45.           loader. removeEventListener ( SecurityErrorEvent. SECURITY_ERROR, handleError );
  46.           loader. removeEventListener ( Event. COMPLETE, handleLoadComplete );
  47.       }
  48. }

TwitterSearchService包含了一个公共函数,getResults()。 它会从 Twitter Search API中获取搜索串的搜索数据。这个函数是通过URLLoader 实现上述功能的,并可直接访问API。 系统为 Actionscript 提供了多个出色的Twitter API 库。 如果您想尝试写一下TweetDeck,我强烈建议您查看一下这些组件。 TwitterSearchService可通过 URLLoader 向对应端点发出一个简单的请求。 URLLoader中添加了监听器,而且错误和结果处理器可获取任何返回的数据。 实际上,handleLoadComplete 除了用来关闭监听器以外,并没有其他的功能,所以它并不是很有用,但很快我们就会解决这个问题。

请注意,TwitterSearchService扩展了 Actor,并实现 了ISearchService。 在这里,Actor只是提供了便捷的通向 EventDispatcher的入口。 结合了services, ISearchService通常被认为是最佳的实现接口。 让我们来看一下ISearchService:

ISearchService.as

   
   
   
   
  1. public interface ISearchService
  2. {
  3.      function getResults (forQuery : String= "robotlegs" ) : void;
  4. }

当您为一个 service 类实现一个接口时,它可以很好地将service换出。为什么需要这项功能呢?因为当连接到远程服务时,它能立即产生有效的模拟数据。您可以让 MockTwitterJsonService每次都返回相同的静态,硬编码的结果。这种方法在单元测试中是相当关键的,这一点以及相关的普通开发工作将 会在另一篇文章中提到。您或许多次感觉到自己的开发工作与团队的sevice开发是并行的。创建以及替换模拟服务的能力提供了很大的开发灵活性。

在我们对从远程服务上返回的数据进行解析之前,您应该对Robotlegs service 的定义有所了解。 在我们正在使用的Twitter Search API 端口实例中,我们得到的结果是一个 JSON字符串。 JSON数据会占用很大的内存空间,但是我们并不希望在我们的应用程序中使用未经处理的JSON字符串。 这样就失去了强类型的好处,而且,我们希望能筛选出在我们的应用程序中使用到的数据。 在这种情况下,我们只需要用户名称和实际的tweet 文本。

Tweet.as

   
   
   
   
  1. public class Tweet
  2. {
  3.      public var user : String;
  4.      public var message : String;
  5. }

这个类需要一些补充, 尽管并不需要太多的补充,但是我们需要一些程序来转化结果。

TwitterSearchResultParser.as

   
   
   
   
  1. public class TwitterSearchResultParser implements ISearchResultParser
  2. {
  3.      public function parseSearchResults (results : Object ) : Array
  4.      {
  5.           var decoder :JSONDecoder = new JSONDecoder ( String (results ) );
  6.           var resultObjects : Array = decoder.getValue ( ).results;
  7.           var tweets : Array = [ ];
  8.           for each ( var result : Object in resultObjects )
  9.           {
  10.                var tweet :Tweet = new Tweet ( );
  11.                tweet.user = result.from_user;
  12. tweet. message = HTMLStringTools.htmlUnescape (result. text );
  13.               
  14.   tweets. push (tweet )
  15.            }
  16.           return tweets;
  17.       }
  18. }

结果解析器是一个简单的公共类。 同理,sevice实现了一个接口,其结果解析器也是如此。 它有一个parseSearchResults()方法来导入结果参数。 Twitter返回的结果是一个字符串,但也可能是XML,例如在我们准备用本地的模拟数据或者变更我们正在访问的Twitter服务时。 JSON字符串将被编码到对象之中,然后会转变成我们的Tweet值对象。 所得的Array将作为结果返回。 现在分析器已经准备就绪,我们将它提供给服务,以便它可以解析结果。

您有几种可选的操作来设置解析类的服务访问。您可以将它的方法设置成static,还可以在服务中创建一个解析类的实例,或者可以利用依赖注入来提供服务类的解析。前两个操作的问题在于,若我们随后需要将解析类交换出来,将会很难办到。

我们现在处于依赖注入的领域中,那为什么不将解析器映射成注入呢?因为如果我们这样做的话,它在我们的上下文中将是一条单独的线条,并且如果我们想 交换出来,它会发生改变。在类之中没有线条和变化类型的注释。因为解析器和服务都会都会与接口一致。如果您想查看交换服务有多么容易,请查阅Bobotlegs Demo Bundle中的Flickr Image Gallery。

让我们在上下文中来看看服务和解析器接口之间的映射:

ServiceExampleContext.as

   
   
   
   
  1. override public function startup ( ) : void
  2. {
  3.      mediatorMap.mapView (SearchResultsView, SearchResultsViewMediator );
  4.  
  5.      injector.mapSingletonOf (ISearchService, TwitterSearchService );
  6.      injector.mapSingletonOf (ISearchResultParser, TwitterSearchResultParser );
  7. }

这是我们第一次使用injector中的mapSingletonOf()方法。 它与mapSingleton的行为差不多是一样的,但有一个关键的区别。 使用mapSingleton(),您可以通过它的接口或基类将类映射成您想要注射的类型。 让我们来看看TwitterSearchService以及如何将它注入到我们的类中:

TwitterSearchService.as

   
   
   
   
  1. private var _parser :ISearchResultParser;
  2.  
  3. [Inject ]
  4. public function set parser ( value :ISearchResultParser ) : void
  5. {
  6.      _parser = value;
  7. }
  8.  
  9. private var loader : URLLoader;
  10.  
  11. public function TwitterSearchService ( )
  12. {
  13. loader = new URLLoader ( );
  14. }

有了以上添加到TwitterSearchService的代码,您现在就可以将映射到里面的解析器注射进去了。这里会发生一些有趣的事情。您会注 意到,解析器是ISearchResultParser的类型。由于我们使用了mapSingletonOf()来映射我们的解析器,它会寻找我们映射的 基类或者接口类型的属性。它实际上会注射TwitterSearchResultParser,但是我们将通过它的接口来访问它。这种方法就是多态,它是 一个面向对象的概念。

我们还将[Inject]标签放在了setter函数的上方,并且它本身并不是属性。 除了我们到现在为止一直使用的属性注射,Robotlegs还支持setter和构造注射。 在这个例子中,我们使用了setter注射来为解析器属性的setter函数提供value值。 构造注射甚至不需要函数中的[Inject]元数据。 我们稍后将会谈论到构造注射,现在需要记住的是为IsearchService接口增加setter函数:

ISearchService.as

   
   
   
   
  1. public interface ISearchService
  2. {
  3.      function getResults (forQuery : String= "robotlegs" ) : void;
  4.  
  5.       function set parser ( value :ISearchResultParser ) : void;
  6. }

现在解析器都已经设置好了,我们准备在服务中实际地使用它。 result句柄是最有可能利用解析器的,因此我们将在这增加一些代码:

TwitterSearchService.as

   
   
   
   
  1. private function handleLoadComplete (event : Event ) : void
  2. {
  3.      var data : Object = loader. data;
  4.      var results : Array = _parser.parseSearchResults ( data );
  5. dispatch ( new SearchResultEvent (SearchResultEvent.RECEIVED, results ) )
  6.      removeLoaderListeners ( );
  7. }

然后继续进行,数据已经从URLLoader中提取出来了,并把它发送给解析器从而转换到一个Tweet对象的数组中,之后派送出一个事件,通知其 它应用程序我们已经得到了结果,然后删除这个事件的监听者。 SearchResultEvent是一个简单的自定义事件,并且它的有效载荷是一个数组:

SearchResultEvent.as

   
   
   
   
  1. public class SearchResultEvent extends Event
  2. {
  3.      public static const RECEIVED : String = "searchResultsReceived";
  4.     
  5.       private var _results : Array;
  6.      public function get results ( ) : Array
  7.      {
  8.           return _results;
  9.       }
  10.  
  11.       public function SearchResultEvent ( type : String, results : Array = null, bubbles : Boolean = false, cancelable : Boolean = false )
  12.      {
  13.           super ( type, bubbles, cancelable );
  14.           _results = results;
  15.       }
  16.  
  17. override public function clone ( ) : Event
  18.      {
  19.           return new SearchResultEvent ( type, results, bubbles, cancelable )
  20.       }
  21. }

到目前的开发为止,我们已经有一个能工作的服务,一个调解视图组件,和一个无法监听的事件。 更准确地说,这是一个从来没有分派过的事件。 我们没有用绑定的服务来调用getResult()。 我们会在SearchResultViewMediator中进行处理:

SearchResultsViewMediator.as

   
   
   
   
  1. public class SearchResultsViewMediator extends Mediator
  2. {
  3.      [Inject ]
  4.      public var view :SearchResultsView;
  5.  
  6.       [Inject ]
  7.      public var service :ISearchService;
  8.  
  9. override public function onRegister ( ) : void
  10.      {
  11.           service.getResults ( );
  12.  
  13.           addContextListener (SearchResultEvent.RECEIVED, handleResultsReceived )
  14.       }
  15.  
  16.       private function handleResultsReceived (event :SearchResultEvent ) : void
  17.      {
  18. view.dataProvider = new ArrayCollection (event.results );
  19.       }
  20. }

现在我们可以对话了! 服务仍然是通过它的接口来注射的,并且在onRegister()中我们调用了getResults()。 在Twitter总部会神奇地产生一些我们的服务解析内容,并且通过携带数据的事件来通知其它应用程序。 mediator会监听那个事件,并且在handleResultsReceived中它会提供dataProvider视图的结果作为一个 ArrayCollection。 我们所需要添加的是ItemRenderer,然后我们将拥有一个完全半功能化的Twitter客户端:

SearchResultItemRenderer.mxml

   
   
   
   
  1. <?xml version= "1.0" ?>
  2. <s :ItemRenderer
  3. xmlns :fx= "http://ns.adobe.com/mxml/2009"
  4. xmlns :s= "library://ns.adobe.com/flex/spark"
  5. xmlns :mx= "library://ns.adobe.com/flex/mx"
  6.         width= "260" height= "85" >
  7.     <fx :Script ><! [CDATA [
  8.         import robotlegs.examples.service.model.Tweet;
  9.  
  10. override public function set data ( value : Object ) : void
  11.         {
  12.              super. data = value;
  13.              var searchResult :Tweet = value as Tweet;
  14.              if (searchResult )
  15.              {
  16.                   message. text = searchResult. message;
  17. username. text = searchResult.user;
  18.               }
  19.              else
  20.              {
  21.                   message. text = "";
  22. username. text = "";
  23.               }
  24.          }
  25. ] ] ></fx :Script >
  26.     <s :layout >
  27. <s :VerticalLayout paddingBottom= "5" paddingRight= "5" paddingLeft= "5" paddingTop= "5" />
  28.     </s :layout >
  29. <s :Label id= "username" width= "100%" maxDisplayedLines= "1" fontWeight= "bold" />
  30. <s :Label id= "message" width= "100%" maxDisplayedLines= "4" />
  31. </s :ItemRenderer >

这样您就有自己的程序了。 tweet列表中提到了Robotlegs。 真是令人愉快啊! 我会鼓励您试玩一下这个应用程序。 比如添加一个控制栏来改变搜索条件。 比如增加登陆的功能,找回您的时间表,或者给张贴一个Tweet。 如果您不希望让您所有讨厌的朋友生气,那就启用一个新的Twitter的帐户来进行开发。 我听说他们有许多额外的用处。

现在我们已经有了服务,为了利用Robotlegs MVC+S的实现,这几乎覆盖了工具的所有范围。 我们唯一没有涉及到的是Commands。 Commands能让我们减弱代码的耦合,并且提供被事件触发的可重用的"行为(actions)"。

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


转载:http://ebibi.com/i/experience/2011/0214/2941.html


你可能感兴趣的:(Robotlegs AS3入门介绍--第三部分:Services(服务))