Cairngorm In One File
最近有机会研究了一下传说中的 Cairngorm 框架,整体感觉还不错,其中的模块化概念可以使项目在变大变复杂之后依然可控。
具体优点就不说了,说说最大的缺点,就是 繁琐 。由于要分离用户触发事件以及对应的 Command 操作,导致几乎每个明显的用户操作都要写一个 Event 类(还真是累阿);另外由于 Cairngorm 本身是一个 design patten 的实践,不是 library,所以造成实际使用的时候需要死版的 implement 一些不着调的空 interface。
简单来说 Cairngorm 是一个婆婆妈妈的 framework,这从技术上来说也不是什么问题,但是重点是会影响写代码人的心情。比如我,一想到要每一个 action 都要写一个没什么特别之处的 Event class 就觉得头疼,一头疼就不想干,一不想干就完了。。。
所以,我就想能不能保留 Cairngorm 的思想,但是用一种更简洁的方式来实现?最后不小心想出了一招,经过一段时间的实验,感觉效果还不错,和大家分享一蛤。
global.as
我把 Cairngorm 的思想融入到一个名叫 global 的类中,使用的时候只需要这一个文件就包含了全部 Cairngorm 相关内容。我打算用一个通过 webservices 查询当前货币兑换比例的例子来具体的说一下 Cairngorm 各方面的特性,以及如何用 global 来实现这些特性。

全局的数据管理
Cairngorm 通过一个 singleton 的 ModelLocator 来连接数据与用户界面,基本思路是全部 ui 相关的数据都不是直接设置到组件上,而是通过一个全局的对象借由 binding 来实现。这样做分离了组件、数据以及命令之间的依靠。当有某个命令修改了某一个数据源之后,不用主动的再去更新组件显示,所有已经设置对应 binding 的组件将会自动更新。
global.as 将自身作为 ModelLocator 来使用,相关代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 | // bindable properties, access from global.data model locator. public var fromCurrency:String=“USD”; public var toCurrency:String=“CNY”; public var currencyResult:String=“”; public var state:String=STATE_LOAD; public var enabled:Boolean=true; /** * The global singleton model locator. */ public static function get data():global { return _data; } |
然后在对应需要响应数据更新的组件上通过绑定 global.data 中的对应属性来实现更新。比如实例中用来显示货币兑换比例结果的 Label 组件绑定了 global.data.currencyResult 属性。
1 | <mx:Label text="{global.data.currencyResult}" color="0x00ff00"></mx:label> |
全局的事件播报以及响应
在 Cairngorm 中,程序通过全局的 EventDispatcher 来播报全部程序相关的事件,然后对应处理事件的 Command 则通过 FrontController 在程序初始阶段配置好与对应事件的监听关系。副作用是:随着程序复杂程度的增加,会出现一堆只是名字不同的 Event class,以及只为了 execute 一下下却不得不实现的 ICommand 类。
global.as 通过静态方法 fire/listen/unlisten 实现了全局的事件播报以及监听功能,其中为了避免过多 Event class 的出现,fire 方法会播报出 DynamicEvent,并允许设置任意参数到 event 对象上,在实例中当用户点击了 Calculate Conversion Rate 按钮后会这样触发事件
1 | click="global.fire(global.EVENT_CONVERSION_RATE,{fromCurrency:fromField.text,toCurrency:toField.text})" |
另一方面 command 响应事件,也做了相应的简化:直接用 function 作为 command 来进行实际的业务操作:
1 2 3 4 5 6 7 |
最后在程序初始化的时候将 command 和对应的事件建立联系(类似 Cairngorm 中 FrontController 的功能)
1 2 3 4 5 6 | function registerDefaultCommands():void { //register default commands here. global.listen(global.EVENT_LOAD_WSDL,loadWsdl); global.listen(global.EVENT_CONVERSION_RATE,conversionRate); } |
全局的网络服务资源
最后关于远程 service 的部分,Cairngorm 提供了另外一个 singleton 的 ServiceLocator 供程序使用,并通过 Responder 来响应服务返回的结果。
在 global.as 中通过一个 rpc 方法实现了注册以及使用 service 的功能。比如实例中 webservices 的注册如下:
1 2 3 4 5 6 7 | function registerDefaultServices():void { //register default services here. var s:WebService=new WebService(); //.. global.rpc(global.CONVERSION_DESTINATION,s); } |
然后代码通过以下方法访问上面注册的 service:
1 2 3 4 | public static function get currencyConvertor():* { return rpc(CONVERSION_DESTINATION); } |
实际上 rpc 方法返回的是一个 service 的 proxy 对象。和直接使用 service 不同的是,通过 proxy 简化了服务方法调用后结果响应部分的代码。比如实例中获取货币兑换比率的方法是这样写的:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
其中第一个函数处理成功,第二个函数处理失败,第三个函数无论成功失败都会先执行。
至此 Cairngorm 的全部功能都被 global.as 实现了。如果有盆友对我瞎掰的这些感兴趣,可以 下载 sample source 然后把你的想法告诉我。
Over
