Skip to main content

[Flex] Cairngorm 練習筆記

由於工作的關係,開始接觸 Adobe 官方建議的微型 MVC 架構 "Cairngorm",剛開始挫折不斷。看了官方的 CairngormStoreWeb 範例教學說明..嗯...看到想睡;在 google 上搜尋了一堆經驗分享文章也都是片片段段,最後終於讓我發現了一個寫的相當簡單易懂的 Getting Started with Cairngorm 教學系列文章 by David Tucker,強烈推薦想要 Cairngorm 快速入門的人一定要看一下!尤其示意圖更是容易理解。

先到 Adobe open source Cairngorm download page 下載最新的 Cairngorm Binary file,想要了解整個架構的可以下載 Source file

以下是我練習的範例:by Flex Builder 3
目標:畫面上只有一個 list component 資料來源為外部 xml
menu tree :


list.xml :

<?xml version="1.0" encoding="utf-8"?>
<data>
<list name="AAAA" data="0"/>
<list name="BBBB" data="1"/>
<list name="CCCC" data="2"/>
<list name="DDDD" data="3"/>
</data>

1. 從 ModelLocator 開始寫 :
使用 Signleton design pattern 重點是將要使用的資料都收集在這邊,由於練習的範例只有顯示一個 list 所以只提供 一個 "list:ArrayCollection" 屬性,(如果複雜的可由 Model class 提供請參考 CairngormStoreWeb 範例寫法 )
package com.model
{
import com.adobe.cairngorm.model.ModelLocator;
import mx.collections.ArrayCollection;

[Bindable]
public class TestModelLocator implements ModelLocator
{
private static var instance:TestModelLocator = null;
private static var allowInstantiation:Boolean = false;

public var list:ArrayCollection;

public function TestModelLocator(){
if (!allowInstantiation) {
throw new Error("Error: Instantiation failed: Use TestModelLocator.getInstance() instead of new TestModelLocator().");
}
list = new ArrayCollection;
}
public static function getInstance() : TestModelLocator {
if ( instance == null ) {
allowInstantiation = true;
instance = new TestModelLocator();
allowInstantiation = false;
}
return instance;
}
}
}


ModelLocator 的用法大概就是:(理解錯誤的話請糾正喔!)
用來收集一堆 app 用的 Models 並提供單一個實體存取所有的資料。[Bindable] 是直接與顯示資料的 Viewer 綁定 (另類的 Observer 模式 ?),也讓 Cairngorm 架構內的 ViewHelper and ViewLocator patterns 變的可有可無 。David Tucker 在文章最後提了幾個重點很有用,其中兩項:ModelLocator 的資料只給 Command or Responder layer 編輯,清楚的釐清工作分責再來就是 不要使用 ViewHelper and ViewLocator (我實在搞不清楚它們的使用意義 ? )。

2. listVO :
package com.vo
{
import com.adobe.cairngorm.vo.IValueObject;

public class listVO implements IValueObject
{
public function listVO(label:String, data:String){
this.label = label;
this.data = data;
}
public var label:String;
public var data:String;
}
}


3. GetListCommand :
Cairngorm Event 傳遞機制就是:
在 FrontComtroller 註冊 Commond 與對應的 Event
-> CairngormEventDispatcher 監聽 Event,爾後操作過程中 CairngormEventDispatcher dispatch Event
-> 實體化相關 Commond 看是編輯 ModelLocator 或者透過 Data Delegate 對外連線並回傳到對應的 Responder 修改 ModelLocator
-> Viewer 更新顯示

感覺起來有點複雜,但是實際走過一遍後會發現它分工很明確,正因為太清楚...感覺開發到最後 Commond 如果沒有好好規劃的話 Events and Commonds 可能會滿天飛,檔案越多當然就代表 debug 越麻煩啦!
package com.commands
{
import com.adobe.cairngorm.commands.ICommand;
import com.adobe.cairngorm.control.CairngormEvent;
import com.bussiness.ListDelegate;
import com.model.TestModelLocator;

import mx.collections.ArrayCollection;
import mx.rpc.IResponder;

public class GetListCommand implements ICommand, IResponder {
private var modelLocator:TestModelLocator = TestModelLocator.getInstance();
public function GetListCommand() {}

public function execute(event:CairngormEvent):void {
//因為要取用外部資料所以需要 Data delegate來輔助
//Commond 都不用管 Delegate 如何取得資料
var delegate:ListDelegate = new ListDelegate( this );
delegate.getList();
}
public function result( event:Object ):void {
//會在 Delegate Layer 中將 result 處理成 ArrayCollection 型態
modelLocator.list = event.result as ArrayCollection;
}
public function fault( event:Object ):void {
//------
}
}
}


4. ListDelegate :
對外部資料的管道,重點是將 server side 的 data 轉換成 application 使用的 data 型態回覆給 Responder ,使用它的好處是:更換 web services 時只需要換掉 data delegate 與修改 Service.mxml 即可。
package com.bussiness {
import com.adobe.cairngorm.business.ServiceLocator;
import com.vo.listVO;

import mx.collections.ArrayCollection;
import mx.rpc.IResponder;
import mx.rpc.events.ResultEvent;

public class ListDelegate {
private var responder:IResponder;
private var service:Object;
public function ListDelegate( responder:IResponder ) {
//responder = GetListCommand
this.responder = responder;
//service 的名稱要看 service.mxml 中設定的 service id
this.service = ServiceLocator.getInstance().getHTTPService( "GetListService" );
}
public function getList():void {
var call:Object = service.send();
/*可以將 service side data 保留在這層處理與 Commond 切分清楚 */
var responder:mx.rpc.Responder = new mx.rpc.Responder(onResult, onFault);
call.addResponder(responder);
}
private function onResult( event:ResultEvent ):void {
var xml:XML = XML(event.result);
var list:ArrayCollection = new ArrayCollection;
for each (var subxml:XML in xml.list){
list.addItem( new listVO ( subxml.@name, subxml.@data));
}
//處理完後送回給 ListCommand
//如果取用 list.xml 的方法轉成 remoting or webService 的話,也只是需要換掉這個 Class
responder.result( new ResultEvent( ResultEvent.RESULT, false, true, list ) );
}
private function onFault( event:Object ):void {
trace(" 載入 list.xml 失敗~~~");
}
}
}


5. Service.mxml :
使用 mxml 來寫超方便的啦!
<?xml version="1.0" encoding="utf-8"?>
<cairngorm:ServiceLocator
xmlns:mx="http://www.adobe.com/2006/mxml
xmlns:cairngorm="com.adobe.cairngorm.business.*">

<mx:HTTPService
url="data/list.xml"
id="GetListService"
resultFormat="e4x"
/>

</cairngorm:ServiceLocator >


6. PageController :
註冊 Commond 到 FrontController

package com.control
{
import com.adobe.cairngorm.control.FrontController;
import com.commands.GetListCommand;

public class PageController extends FrontController
{
public function PageController()
{
super();
//加入 Command ~~
this.addCommand( "getList", GetListCommand );
}

}
}


7. 完成 CairngormTest.mxml
<?xml version="1.0" encoding="utf-8"?>
<mx:Application
xmlns:mx="http://www.adobe.com/2006/mxml"
layout="absolute" applicationComplete="init()">

<mx:Script>
<![CDATA[
import com.control.PageController;
import com.model.TestModelLocator;
import com.adobe.cairngorm.control.CairngormEvent;
import com.adobe.cairngorm.control.CairngormEventDispatcher;
import com.bussiness.Service;

private var service:Service, controller:PageController;

[Bindable]
private var model:TestModelLocator = TestModelLocator.getInstance();
private function init():void{
//實體化 PageController and Service
controller = new PageController;
service = new Service;
//取 list 資料
CairngormEventDispatcher.getInstance().dispatchEvent(new CairngormEvent("getList"));
}
]]>
</mx:Script>
<mx:List dataProvider="{model.list}" />
</mx:Application>


整個練習完後至少對 Cairngorm 有點概念,當然要熟練的實做還需要一些時間的努力啊!=P

Comments

  1. 這篇實在太好用了....正好在看Cairngorm,看到有一點點想抓狂,Erin這篇超清楚的!Thanks!! :D

    ReplyDelete
  2. ViewHelper的作用就是用来做一些画面的辅助工作的。当画面上会有比较复杂的逻辑需要处理的时候,虽然可以直接写到MXML文件里面,但是那样看起来会非常乱,所以一般是把那些逻辑单独地写到一个ViewHelper里面。

    ReplyDelete
  3. 謝謝說明~~^^ 意思就是如果我需要讓 UI component 更單純的話就需要使用 ViewHelper 囉...

    ReplyDelete

Post a Comment

Popular posts from this blog

PureMVC 我也會 [0]

最近感覺 PureMVC 又熱了起來,也剛好好久沒有更新文章了, 就順便將去年底做的企業內訓 PureMVC 課程部分整理寫出來, 要講 PureMVC 當然要先從啥是 MVC 講起: Model-View-Control 出處: 維基百科 MVC ,大概節錄一段: (控制器Controller)- 負責轉發請求,對請求進行處理。 (檢視View) - 介面設計人員進行圖形介面設計。 (模型Model) - 程式設計師編寫程式應有的功能(實作算法等等)、數據庫專家進行資料管理和數據庫設計(可以實作具體的功能)。 其實到 Flash 的世界來講,Model and Control 都是由 .as 處理,而 View 便是 .fla+.as ,為了要鬆綁之間的關係,Event 機制就相當重要。其實每個人對 MVC 的最佳解釋都不同,真的要多練習才會有所領悟。 簡單來說: Model = 餐廳廚房 data: 西餐類 action:依照點菜單做餐點 action: 做完餐點就是將餐點放在出菜口按下通知鈴等服務生來 Control = 服務生 action: 聽到大門歡迎鈴就要說「歡迎光臨」 action: 看到客人揮揮手要去收點菜單 action: 聽到廚房通知鈴看是哪桌的餐點去送菜 View = 餐廳外場 view: 田園式的西餐廳裝潢 action: 客人進門會有歡迎鈴 action: 客人揮揮手叫服務生過來服務,是哪個服務生都無所謂,重點只要會收點菜就行了。 action: 客人收到餐點準備開動 當餐廳要改成外炒店,這時候只需要將大廚換成會中餐廚師,其出的菜就是中式快炒。 當餐廳外場由田園式外觀重新裝潢成華麗感夜店風,其進門的客層也會有所不同。 重點就是當你換掉一個地方時,對其它的部份不會造成太大的影響或者根本無所謂,這就是 MVC 所講求的境界... 一般來說,小專案有沒有必要使用 MVC 就是由各位自己判斷了,當你習慣將程式切分開來,發現 debug 不是一件痛苦的事情時,這時候有沒有強制使用 MVC 倒不是重點,因為你已經養成良好的撰寫習慣。但是開始接觸大型專案配合 team work 時,在沒有一個共用的核心框架前提下,這個專案開發到最後一定會是一個多手多腳的怪物,共用核心框架的價值就在這邊展現,這