原文地址: http://insideria.com/2010/06/an-introduction-to-robotlegs-a-2.html
本文是insideRIA上发表的介绍Robotles AS3系列文章的第三部分。该系列文章的第一部分向我们介绍了什么是Robotles AS3,并通过一个“HelloWorld”例子对Context(上下文)与Mediators(媒介)类进行了简要概述。在第二部分中,我们探索了 Models(模块)的相关内容。而本文将继续拓展前两部分中的内容,并对Services(服务)进行介绍。
如果您没有学习前两部分的内容,我建议您先回头阅读一下:
这个网站的名称是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”的实时结果。 开始的时候我们会尽可能简单地阐述问题,今后有必要的话我们将会加大复杂的程度。 我们来看一下整合的程序片段:
<?xml version= "1.0" ?> <s :Application xmlns :fx= "http://ns.adobe.com/mxml/2009" xmlns :s= "library://ns.adobe.com/flex/spark" xmlns :service= "robotlegs.examples.service.*" xmlns :mx= "library://ns.adobe.com/flex/mx" xmlns :view= "robotlegs.examples.service.view.*" width= "300" height= "300" > <fx :Declarations > <service :ServiceExampleContext contextView= "{this}" /> </fx :Declarations > <view :SearchResultsView /> </s :Application >
这个是主程序视图。在Declarations标签中,您会注意到我已经做好一个可用的ServiceExampleContext实例。如果您没 有了解过第一和第二部分的内容,我建议您现在就学习一下。前两章的内容包括了上下文(Context)的创建和初始化。除了 ServiceExampleContext以外,该应用还添加了SearchResultView的功能。这个示例里只显示了一个视图。请参考一下这里 的上下文(Context):
public class ServiceExampleContext extends Context { override public function startup ( ) : void { mediatorMap.mapView (SearchResultsView, SearchResultsViewMediator ); } }
我们在 SearchExampleContext中为SearchResultsView设计了一个媒介。 SearchResultsView 是 Spark List 组件的一个子类。
<?xml version= "1.0" ?> <s :List xmlns :fx= "http://ns.adobe.com/mxml/2009" xmlns :s= "library://ns.adobe.com/flex/spark" xmlns :mx= "library://ns.adobe.com/flex/mx" itemRenderer= "robotlegs.examples.service.view.renderers.SearchResultItemRenderer" height= "100%" width= "300" > <s :layout ><s :VerticalLayout gap= "0" paddingBottom= "10" paddingRight= "10" paddingLeft= "10" paddingTop= "10" /></s :layout > </s :List >
SearchResultsView 需要一个能与其他应用程序进行数据传递的媒介:
public class SearchResultsViewMediator extends Mediator { [Inject ] public var view :SearchResultsView; override public function onRegister ( ) : void { //Make the magic!(缔造奇迹!) } }
既然 SearchResultsView已经与其他组件连接好,那么接下来我们将开始学习能够连接远程 Twitter server 并给我们返回一些数据的服务程序。 如果您仔细研究SearchResultsView ,将会发现我们也已经给它添加了 ItemRenderer。 我们会粗略地介绍一下。 首先,我们来看一下连接 Twitter 并获得数据的基本服务类:
public class TwitterSearchService extends Actor implements ISearchService { private var loader : URLLoader; public function TwitterSearchService ( ) { loader = new URLLoader ( ); } private const TWITTER_SEARCH_ENDPOINT : String = "http://search.twitter.com/search.json"; public function getResults (forQuery : String = "robotlegs" ) : void { var urlRequest : URLRequest = new URLRequest (TWITTER_SEARCH_ENDPOINT ); var params : URLVariables = new URLVariables ( ); params.q = HTMLStringTools.htmlEscape (forQuery ); params.rpp = 100; urlRequest. data = params; addLoaderListeners ( ); loader. load (urlRequest ); } private function handleError (event : SecurityErrorEvent ) : void { removeLoaderListeners ( ); } private function handleLoadComplete (event : Event ) : void { removeLoaderListeners ( ); } private function addLoaderListeners ( ) : void { loader. addEventListener ( IOErrorEvent. IO_ERROR, handleError ); loader. addEventListener ( SecurityErrorEvent. SECURITY_ERROR, handleError ); loader. addEventListener ( Event. COMPLETE, handleLoadComplete ); } private function removeLoaderListeners ( ) : void { loader. removeEventListener ( IOErrorEvent. IO_ERROR, handleError ); loader. removeEventListener ( SecurityErrorEvent. SECURITY_ERROR, handleError ); loader. removeEventListener ( Event. COMPLETE, handleLoadComplete ); } }
TwitterSearchService包含了一个公共函数,getResults()。 它会从 Twitter Search API中获取搜索串的搜索数据。这个函数是通过URLLoader 实现上述功能的,并可直接访问API。 系统为 Actionscript 提供了多个出色的Twitter API 库。 如果您想尝试写一下TweetDeck,我强烈建议您查看一下这些组件。 TwitterSearchService可通过 URLLoader 向对应端点发出一个简单的请求。 URLLoader中添加了监听器,而且错误和结果处理器可获取任何返回的数据。 实际上,handleLoadComplete 除了用来关闭监听器以外,并没有其他的功能,所以它并不是很有用,但很快我们就会解决这个问题。
请注意,TwitterSearchService扩展了 Actor,并实现 了ISearchService。 在这里,Actor只是提供了便捷的通向 EventDispatcher的入口。 结合了services, ISearchService通常被认为是最佳的实现接口。 让我们来看一下ISearchService:
public interface ISearchService { function getResults (forQuery : String= "robotlegs" ) : void; }
当您为一个 service 类实现一个接口时,它可以很好地将service换出。为什么需要这项功能呢?因为当连接到远程服务时,它能立即产生有效的模拟数据。您可以让 MockTwitterJsonService每次都返回相同的静态,硬编码的结果。这种方法在单元测试中是相当关键的,这一点以及相关的普通开发工作将 会在另一篇文章中提到。您或许多次感觉到自己的开发工作与团队的sevice开发是并行的。创建以及替换模拟服务的能力提供了很大的开发灵活性。
在我们对从远程服务上返回的数据进行解析之前,您应该对Robotlegs service 的定义有所了解。 在我们正在使用的Twitter Search API 端口实例中,我们得到的结果是一个 JSON字符串。 JSON数据会占用很大的内存空间,但是我们并不希望在我们的应用程序中使用未经处理的JSON字符串。 这样就失去了强类型的好处,而且,我们希望能筛选出在我们的应用程序中使用到的数据。 在这种情况下,我们只需要用户名称和实际的tweet 文本。
public class Tweet { public var user : String; public var message : String; }
这个类需要一些补充, 尽管并不需要太多的补充,但是我们需要一些程序来转化结果。
public class TwitterSearchResultParser implements ISearchResultParser { public function parseSearchResults (results : Object ) : Array { var decoder :JSONDecoder = new JSONDecoder ( String (results ) ); var resultObjects : Array = decoder.getValue ( ).results; var tweets : Array = [ ]; for each ( var result : Object in resultObjects ) { var tweet :Tweet = new Tweet ( ); tweet.user = result.from_user; tweet. message = HTMLStringTools.htmlUnescape (result. text ); tweets. push (tweet ) } return tweets; } }
结果解析器是一个简单的公共类。 同理,sevice实现了一个接口,其结果解析器也是如此。 它有一个parseSearchResults()方法来导入结果参数。 Twitter返回的结果是一个字符串,但也可能是XML,例如在我们准备用本地的模拟数据或者变更我们正在访问的Twitter服务时。 JSON字符串将被编码到对象之中,然后会转变成我们的Tweet值对象。 所得的Array将作为结果返回。 现在分析器已经准备就绪,我们将它提供给服务,以便它可以解析结果。
您有几种可选的操作来设置解析类的服务访问。您可以将它的方法设置成static,还可以在服务中创建一个解析类的实例,或者可以利用依赖注入来提供服务类的解析。前两个操作的问题在于,若我们随后需要将解析类交换出来,将会很难办到。
我们现在处于依赖注入的领域中,那为什么不将解析器映射成注入呢?因为如果我们这样做的话,它在我们的上下文中将是一条单独的线条,并且如果我们想 交换出来,它会发生改变。在类之中没有线条和变化类型的注释。因为解析器和服务都会都会与接口一致。如果您想查看交换服务有多么容易,请查阅Bobotlegs Demo Bundle中的Flickr Image Gallery。
让我们在上下文中来看看服务和解析器接口之间的映射:
override public function startup ( ) : void { mediatorMap.mapView (SearchResultsView, SearchResultsViewMediator ); injector.mapSingletonOf (ISearchService, TwitterSearchService ); injector.mapSingletonOf (ISearchResultParser, TwitterSearchResultParser ); }
这是我们第一次使用injector中的mapSingletonOf()方法。 它与mapSingleton的行为差不多是一样的,但有一个关键的区别。 使用mapSingleton(),您可以通过它的接口或基类将类映射成您想要注射的类型。 让我们来看看TwitterSearchService以及如何将它注入到我们的类中:
private var _parser :ISearchResultParser; [Inject ] public function set parser ( value :ISearchResultParser ) : void { _parser = value; } private var loader : URLLoader; public function TwitterSearchService ( ) { loader = new URLLoader ( ); }
有了以上添加到TwitterSearchService的代码,您现在就可以将映射到里面的解析器注射进去了。这里会发生一些有趣的事情。您会注 意到,解析器是ISearchResultParser的类型。由于我们使用了mapSingletonOf()来映射我们的解析器,它会寻找我们映射的 基类或者接口类型的属性。它实际上会注射TwitterSearchResultParser,但是我们将通过它的接口来访问它。这种方法就是多态,它是 一个面向对象的概念。
我们还将[Inject]标签放在了setter函数的上方,并且它本身并不是属性。 除了我们到现在为止一直使用的属性注射,Robotlegs还支持setter和构造注射。 在这个例子中,我们使用了setter注射来为解析器属性的setter函数提供value值。 构造注射甚至不需要函数中的[Inject]元数据。 我们稍后将会谈论到构造注射,现在需要记住的是为IsearchService接口增加setter函数:
public interface ISearchService { function getResults (forQuery : String= "robotlegs" ) : void; function set parser ( value :ISearchResultParser ) : void; }
现在解析器都已经设置好了,我们准备在服务中实际地使用它。 result句柄是最有可能利用解析器的,因此我们将在这增加一些代码:
private function handleLoadComplete (event : Event ) : void { var data : Object = loader. data; var results : Array = _parser.parseSearchResults ( data ); dispatch ( new SearchResultEvent (SearchResultEvent.RECEIVED, results ) ) removeLoaderListeners ( ); }
然后继续进行,数据已经从URLLoader中提取出来了,并把它发送给解析器从而转换到一个Tweet对象的数组中,之后派送出一个事件,通知其 它应用程序我们已经得到了结果,然后删除这个事件的监听者。 SearchResultEvent是一个简单的自定义事件,并且它的有效载荷是一个数组:
public class SearchResultEvent extends Event { public static const RECEIVED : String = "searchResultsReceived"; private var _results : Array; public function get results ( ) : Array { return _results; } public function SearchResultEvent ( type : String, results : Array = null, bubbles : Boolean = false, cancelable : Boolean = false ) { super ( type, bubbles, cancelable ); _results = results; } override public function clone ( ) : Event { return new SearchResultEvent ( type, results, bubbles, cancelable ) } }
到目前的开发为止,我们已经有一个能工作的服务,一个调解视图组件,和一个无法监听的事件。 更准确地说,这是一个从来没有分派过的事件。 我们没有用绑定的服务来调用getResult()。 我们会在SearchResultViewMediator中进行处理:
public class SearchResultsViewMediator extends Mediator { [Inject ] public var view :SearchResultsView; [Inject ] public var service :ISearchService; override public function onRegister ( ) : void { service.getResults ( ); addContextListener (SearchResultEvent.RECEIVED, handleResultsReceived ) } private function handleResultsReceived (event :SearchResultEvent ) : void { view.dataProvider = new ArrayCollection (event.results ); } }
现在我们可以对话了! 服务仍然是通过它的接口来注射的,并且在onRegister()中我们调用了getResults()。 在Twitter总部会神奇地产生一些我们的服务解析内容,并且通过携带数据的事件来通知其它应用程序。 mediator会监听那个事件,并且在handleResultsReceived中它会提供dataProvider视图的结果作为一个 ArrayCollection。 我们所需要添加的是ItemRenderer,然后我们将拥有一个完全半功能化的Twitter客户端:
<?xml version= "1.0" ?> <s :ItemRenderer xmlns :fx= "http://ns.adobe.com/mxml/2009" xmlns :s= "library://ns.adobe.com/flex/spark" xmlns :mx= "library://ns.adobe.com/flex/mx" width= "260" height= "85" > <fx :Script ><! [CDATA [ import robotlegs.examples.service.model.Tweet; override public function set data ( value : Object ) : void { super. data = value; var searchResult :Tweet = value as Tweet; if (searchResult ) { message. text = searchResult. message; username. text = searchResult.user; } else { message. text = ""; username. text = ""; } } ] ] ></fx :Script > <s :layout > <s :VerticalLayout paddingBottom= "5" paddingRight= "5" paddingLeft= "5" paddingTop= "5" /> </s :layout > <s :Label id= "username" width= "100%" maxDisplayedLines= "1" fontWeight= "bold" /> <s :Label id= "message" width= "100%" maxDisplayedLines= "4" /> </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