最近有机会研究了一下传说中的 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
| public function conversionRate (e :DynamicEvent ):void
{
//..
var fromCurrency :String= String(e .fromCurrency );
var toCurrency :String= String(e .toCurrency );
//..
} |
最后在程序初始化的时候将 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
| global.currencyConvertor .ConversionRate (fromCurrency ,toCurrency )(
function(data:Object):void
{
global.data.currencyResult= data.result .toString();
},
function(info:Object):void
{
Alert .show(info.fault .message,info.fault .faultString );
},
function(o :*):void
{
global.data.enabled= true;
}); |
其中第一个函数处理成功,第二个函数处理失败,第三个函数无论成功失败都会先执行。
至此 Cairngorm 的全部功能都被 global.as 实现了。如果有盆友对我瞎掰的这些感兴趣,可以 下载 sample source 然后把你的想法告诉我。
Over