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
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 :P

Leave a Reply

 

 

 

You can use these HTML tags

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>