做Flex做久了做大了,就會覺得之前寫的的Flex代碼開始有點亂,哪怕你寫的規范了,但總覺得結構松散,維護不方便,相信很多人剛開始做Flex的時候,都是想到什么功能,就寫什么功能,或者有些好點的,就先畫了個大體的流程圖之類的,因為現在Flex普及得還不夠,很多人做Flex也是試探階段,不敢用作商業項目或其它大項目,只會用來試水技術層面的,所以都是做些小應用的多,就會忽略了設計一個比較好的框架來開發。所以Flex的開發框架就應運而生了。目前,好的Flex開發框架還不多,官方有個Cairngorm的框架,可能有些人會說這個框架有點復雜,其實不然,對比起Ruby的Rails,Java的Struts,Spring之類的開發框架,就顯得簡單得多了。只要清楚了解要MVC的概念,就會對這些框架并不陌生,但是今天的主角不是Cairngorm,而是另一個Flex框架 PureMVC,如果說Cairngorm復雜的話,那么PureMVC就顯得簡單多了,PureMVC比較輕盈,核心也只有十來個類,是一個輕量級的Flex框架,但PureMVC的通用性還是比較廣的,有PHP的,有Java的有Python的??赡苤苯诱f框架的使用會比較抽象,那么就由一個實例來開始講解吧,就用一個PureMVC做的一個MP3播放器。
先來看看PureMVC的結構圖:
在圖中,Facade,Model,View,Controller都是PureMVC的四個核心類,都是單例模式的,用戶無需操作那Model,View,Controller類,而用戶只需要操作Facade就夠了,Facade類用來管理其它的三個單例類,顧名思義,那三個類都是分別對應 MVC 模式的那三個元素,Facade也是個單例,它負責創建,激活,調用其它的三個類,管理MVC各屋的生命周期。
而我們看看Model類,又細分了一個Proxy類出來,我們稱其為代理吧,就是對數據模型的一個代理,負責訪問我們的數據對象(Data Object)也就是Cairngorm中的ValueObject,其實都是同一個概念。而類結構上,對數據操作的代理Proxy類就只有一個,但可以從我們的應用上又分為Local Proxy,Remote Proxy,其實都只是Proxy ,只是根據用戶的應用的不同,在Proxy里面實現不同的功能而已,比如如果你操作本地數據(內存中的數據,并非本地操作系統的文件),你可以寫一些VO的getter/setter直接操作數據,而如果是Remote的數據的話,你可以在Proxy類里定義一些HttpService,URLLoader,WebService等等的訪問遠程數據的API,之后將獲取到的遠程數據放在VO中。
在Controller類里分出一個叫Command的類來,直接翻譯的話,就是“命令”類,通常這些類都是用來處理一些業務流程,某些算法操作等等的操作。比如現在用戶單擊了“獲取數據”的按鈕,程序將從Proxy類里訪問服務器,服務器返回數據之后,那些都是程序看得懂的數據,比如是XML,而如果數據結構比較復雜,你不可能直接將數據顯示給用戶看吧?那就將解析這些數據的工作交給Command來做,比如寫一個ParseCommand的類,將獲得的XML數據傳遞給該Command,在Command里進行數據的過濾,排列,整理等等的功能。再將組積好后數據交給Mediator來進行顯示,而Mediator,就是下面我們要說的。
在View類里分出一個Mediator的類,該類是用來對ViewComponent操作的,我們暫且叫它“中介類”吧,為什么叫“中介”呢?其實就是用戶界面(UI)與程序結構邏輯之間的中介,因為用戶在界面上的操作,比如Button的Click事件不是直接反映到Command或者Proxy類上的,而是反映給Mediator類,在Mediator里作一些簡單處理,比如驗證合法性,觸發其它ViewComponent的狀態等,在這里也會將用戶的數據封裝在VO里面,再交由Command或Proxy來進一步處理?;旧螹ediator只對用戶的操作或用戶提交的數據進行封裝并簡單預處理,而業務邏輯,存儲服務的就應交給Command和Proxy來做,這樣MVC分工好,使得程序結構比較嚴緊,可讀性強,實現松耦合。當你改變了UI時,只需要對Mediator進行相應的改變就行了,而你改變了業務的邏輯與算法之類的話,也相應的改變Command就可以了,對其它模塊的影響不大。
在上面這個圖中,沒有列出來的一個很重的類,就是 Notification 類,這個類為什么十分重要,可以說也是PureMVC的潤滑劑,因為他是連接MVC各大部分的一個消息機制,就像是Cairngome里面的CairngomeEvent與FrontController,為了實現更好的松耦合,就是靠這個消息機制,因為各大部分中,很少直接的引用調用,而是以“發消息”(或者說是通知吧)來相互數據交流與通訊,這里是很好的使用了“觀察者模式”,因此,在某一部分改變的處理邏輯的話,只是它所發送的消息沒有改變,或者所偵聽的消息沒有改變,那么就不會影響到其它部分。
另外要注意幾點,Command類是短生命周期的,也就是說,當有消息通知需要用到該Command進行處理時,Facade就會創建這個Command類,并將數據傳入Command里面進行處理,當處理完成后,其生命周期就會結束,所以不要將一些長生命周期的數據存放在Command里,比如不要將一些狀態數據信息存放在Command里面。還有就是Proxy類只會發送“消息”(通知),而不會接收任何消息,而Mediator與Command則可以發送與接收,所以你不能直接發消息通知Proxy去加載數據,而是通過引用Proxy的實例調用相關的函數。理論就說了一大堆了,我們來看看那個MP3播放器實例吧!
我們先來看看主程序的代碼,PureMVC的入口點:
1 <?xml version="1.0" encoding="utf-8"?>
2 <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
3 xmlns:view="com.jiangzone.flex.pureplayer.view.ui.*"
4 verticalGap="2"
5 layout="vertical" creationComplete="fadace.startup(this)"
6 backgroundColor="0x444444">
7 <mx:Style>
8 //這里的CSS代碼略去
9 </mx:Style>
10
11 <mx:Script>
12 <![CDATA[
13 import com.jiangzone.flex.pureplayer.ApplicationFacade;
14
15 private var fadace:ApplicationFacade = ApplicationFacade.getInstance();
16
17 ]]>
18 </mx:Script>
19 <mx:Box width="131">
20 <view:ProgressBoard id="progressBoard" />
21 <view:ControlBoard id="controlBoard" />
22 <view:SongListBoard id="songListBoard" />
23 </mx:Box>
24
25 </mx:Application>
從上面代碼我們看到,定義了一個 facade 這個就是Facade的一個實例,而ApplicationFacade是繼承自Facade類的,這個就是PureMVC的整個架構的控制管理類,因為Facade是一個單例,所以不能直接new 的,所以在ApplicationFacade里面定義了一個靜態方法來獲取它的實例。在程序的createComplete事實觸發的時候,我們就調用facade.startup(this)這個方法,意思就是啟動整個框架。
這里的代碼都比較簡單,我們再來看看ApplicationFacade的代碼:
1 package com.jiangzone.flex.pureplayer
2 {
3 import org.puremvc.as3.interfaces.IFacade;
4 import org.puremvc.as3.patterns.facade.Facade;
5 import org.puremvc.as3.patterns.observer.Notification;
6
7 import com.jiangzone.flex.pureplayer.controller.StartupCommand;
8
9 public class ApplicationFacade extends Facade implements IFacade
10 {
11 // Notification name constants
12 public static const STARTUP:String = "startup";
13
14
15 /**
16 * Singleton ApplicationFacade Factory Method
17 */
18 public static function getInstance() : ApplicationFacade {
19 if ( instance == null ) instance = new ApplicationFacade( );
20 return instance as ApplicationFacade;
21 }
22
23 /**
24 * Start the application
25 */
26 public function startup(app:Object):void
27 {
28 sendNotification( STARTUP, app );
29 }
30
31 /**
32 * Register Commands with the Controller
33 */
34 override protected function initializeController( ) : void
35 {
36 super.initializeController();
37 registerCommand( STARTUP, StartupCommand );
38 }
39
40 }
41 }
這里分析一下,在ApplicationFacade類里,我們定義了一個String的常量,這個只是一個消息的類型,跟Flex里的Event的常量一樣的,注意,規范化一點的話,應該將消息類型的字符串都定義為常量,而我在后面的代碼中為了省事就直接用“XXXXX”這樣的字串代替了,還是建義寫成靜態常量。
我們看到了startup()的代碼了,就是在剛才主程序里調用的那個函數,這里接收了一個主程序的引用。
我們還看到了有一個initializeController( ) 的函數,當這個ApplicationFacade被實例化加載的時候,會先自動調用initializeController( ) 這個函數,所以,我們應當在ApplicationFacade在被初始化的時候,就對Command進行注冊,說就是注冊,其實也只是將一個Command與一個消息綁定在一起而已。當發送該消息時,Facade就會自動的找到那個Command并實例化執行。
registerCommand( STARTUP, StartupCommand );這句就是對Command進行注冊的代碼,registerCommand都是父類或接口里面定義的方法,我們先不用管它,STARTUP就是上面定義的一個常量,表示一個消息的類型,StartupCommand這個類就是我定義的一個Command類,這里說白了就是STARTUP這個字符串常量就是一個Key,而StartupCommand就是一個Value,存放在一個數組里面,當有人發送一個STARTUP的消息時,程序就自動生成一個StartupCommand來處理。
我們再看看startup()這個方法,在剛才的主程序里調用這個方法時,傳入了一個傳入了一個參數this,就是主程序本身,在startup()方法里面,發送了一個消息 sendNotification( STARTUP, app ); sendNotification()這個是發送消息的方法,第一個參數是消息的類型,第二個是可選參數,是消息的內容(消息體),在這里,將主程序的引用作為消息體綁在消息里一起發送了。由于之前在初始化的時候將STARTUP消息類型與StartupCommand綁定在一起了,所以當發送這個消息的時候,StartupCommand將會被通知,所以這時候,程序的流程就跳入到StartupCommand類里面。下面我們來看StartupCommand類的內容:
1 package com.jiangzone.flex.pureplayer.controller
2 {
3 import org.puremvc.as3.interfaces.ICommand;
4 //這里略去一些import代碼
5 import com.jiangzone.flex.pureplayer.model.PlayListProxy;
6
7 public class StartupCommand extends SimpleCommand implements ICommand
8 {
9 override public function execute( note:INotification ) : void
10 {
11 /**
12 * 獲取消息體內容,在發送STARTUP消息時,將主程序PurePlayer作為消息體跟隨消息傳送
13 */
14 var app:PurePlayer = note.getBody() as PurePlayer;
15
16 /**
17 * 注冊代理(Model)
18 */
19 facade.registerProxy(new SongProxy());
20 facade.registerProxy(new PlayListProxy());
21 /**
22 * 注冊ViewComponents或者UI組件的中介器。
23 */
24 facade.registerMediator(new ControlBoardMediator(app.controlBoard));
25 facade.registerMediator( new SongListBoardMediator(app.songListBoard));
26 facade.registerMediator(new ProgressBoardMediator(app.progressBoard));
27
28 (facade.retrieveProxy(PlayListProxy.NAME) as PlayListProxy).loadPlayList();
29 }
30 }
31 }
上面的就是一個Command的代碼,注意,一個Command必需要實現ICommand接口,而如果是一個單Command的話,就需要繼承SimpleCommand類,而如果是一個Command鏈的話,就需要實現MacroCommand,至于Command鏈,如果有J2EE基礎的話,也就是Filter的過濾器鏈差不多。這里不多說。大家可以看看PureMVC的官方文檔與API!
在Command里,都需要覆蓋execute 這個方法,這個方法就是執行你的邏輯代碼的地方,由Facade自動調用,當這個Command所綁定的消息被發送時,Facade就會創建一個Command實例并調用execute方法,方法里還傳入一個INotification參數,就是你所發送的那個消息,里面包含了消息類型名稱與消息體。在這個Command里,我沒有處理什么,因為這個Command用于啟動程序框架的,所以只在這里初始化了一些程序需要用到的資源,如注冊代理與注冊中介器,我們可以看到,注冊代理與注冊中介器的方法與注冊Command的方法不同,注冊Command的話,需要一個消息名稱與一個Command類綁定,而代理與中介器的注冊就不需要與消息綁定,直接將代理與中介器實例化之后進行注冊就可以了。這是由于Command的生產控制不是由用戶來操作的,是由Facade里面的一個工廠方法來對Command實例化并管理的,所以需要與一個消息名稱進行綁定,而代理與中介器就是用戶管理的,通常一個代理就對應一個數據結構,如果有幾個數據結構都比較簡單,就可以在一個Proxy里管理,而同理,中介器也一樣,一個中介器對一個Flex組件,但為一個Button建立一個中介器未名太浪費了,所以我這里都是將MP3分成三個部分,控制按鈕部分,歌曲列表部分,播放進度部分,三個部分用三個中介器。通常這些中介器或者Proxy創建一次就可以了,Facade會將它它存放在數組中,當需要用到時,再由Facade來獲取他們,所以,在注冊代理與中介的時候,先實例化它們再注冊!因為Command是短生命周期,而Proxy與Mediator是長生命周期,所以這里與Command有點區別。在實例化中介器的時候,我傳入一個app.controlBoard的值:
new ControlBoardMediator(app.controlBoard),就是說,我這個中介器是對應app.controlBoard這個控件,app就是主程序。
當所有需要的資源都注冊好后,我執行了下面一句代碼:
(facade.retrieveProxy(PlayListProxy.NAME) as PlayListProxy).loadPlayList();
之前講到,注冊代理時,將代理的實例進行注冊,實際上就只是在Facade里將這個代理實例放進數組里而已,所以用facade.retrieveProxy()這個方法可以再次獲得那個實例的引用,再調用這個代理里的一個方法loadPlayList()來進行加載播放列表。上上上面已經說過,因為Proxy是只可以發信息,不可以收信息,所以你叫Proxy工作的話,只好得到它的引用再調用它的方法來控件它的工作。注意,在Facade重新獲得代理的方法里facade.retrieveProxy(PlayListProxy.NAME) as PlayListProxy 你需要指定一個字符串來獲取某一個代理,在編寫每一個代理時,都要為它指定一個name的字符串,而且是代理的唯一標識!這個時候,程序的流程就會跳到代理里運行loadPlayList()這個方法。下面,我們來看看PlayListProxy的代碼:
1 package com.jiangzone.flex.pureplayer.model
2 {
3 import org.puremvc.as3.interfaces.IProxy;
4 //省略import代碼

5 import flash.xml.XMLNode;
6
7 public class PlayListProxy extends Proxy implements IProxy
8 {
9 //定義一個代理的名字,唯一的標識
10 public static const NAME:String = 'PlayListProxy';
11 //定義一個HttpService,用于獲取遠程的數據
12 private var hs:HTTPService;
13
14 public function PlayListProxy():void{
15 super(NAME,new Array());
16 hs = new HTTPService();
17 hs.addEventListener(ResultEvent.RESULT,onResult);
18 hs.addEventListener(FaultEvent.FAULT,onFault);
19 }
20
21 public function get playList():Array{
22 return data as Array;
23 }
24
25 public function loadPlayList(url:String = 'jiang/pureplayer/data/playlist.xml'):void{
26 hs.method = "GET";
27 hs.resultFormat = "xml";
28 hs.url = url + '?ranid=' + (new Date()).time;
29 hs.send();
30 }
31
32 private function onFault(e:FaultEvent):void{
33
34 }
35
36 private function onResult(e:ResultEvent):void{
37 var arr:Array = data as Array;
38 var xmlNode:XMLNode = e.result as XMLNode;
39 for(var i:String in xmlNode.childNodes){
40 var obj:Object = new Object();
41 var node:XMLNode = xmlNode.childNodes[i];
42 obj["label"] = node.attributes.label;
43 obj["data"] = node.attributes.data;
44 arr.push(obj);
45 }
46 sendNotification("GET_PLAYLIST_COMPLETE",data);
47 }
48 }
49 }
很累呀。。。休息一會,抽根煙繼續!
來分析一下代碼,在該代理被實例化時,會調用super(NAME,new Array());這個父類的方法,其實Proxy的構造函數接收兩個參數,一個是Proxy的唯一標識名字,另一個就是所需要作代理的數據,是一個Object,就是說,你的這個Proxy類要對哪些數據作代理呢?就是這個值傳入的,在Proxy基類里面有一個data的屬性,這個屬性就是存放你的實際數據,所以在我的這個PlayListProxy構造時,我創建了一個Array來作為我的數據,因為我這個代理,是代理一個播放列表數據的,所以我將用Array來存放我的播放列表數據。所以就將new Array()的值交給父類構造器里,父類構造器將會對該Array存放在data這個變量屬性里。由于data是Object類型的,所以我們獲取這個data的時候,還要對其轉換成相應的類型,所以為了方便,我寫了一個getter方法來封裝這個操作:
1 public function get playList():Array{
2 return data as Array;
3 }
以后可以直接調用playList屬性來獲取Array類型的數據了。
而在 loadPlayList 這個函數里,接收一個可選參數url,通過HttpService來獲取該url中的xml數據。
按照上上上上上面說的那樣,Proxy只有一個,但你可以按不同用途分為Local Proxy,與Remote Proxy,而這里它的作用就是Remote Proxy,因為他是操作遠程數據。
當接收數據成功時,調用了 onResult 方法,里面進行了XML簡單的分析,其實可以將該分析操作交給一個Command來完成,但是這里確實是太簡單的分析了,就沒有必交給Command了,自已來完成吧。當解析完成時,發送了一個消息sendNotification("GET_PLAYLIST_COMPLETE",data);
這里要說明一下,這里用了發消息來通知其它模塊說我已獲得數據了,你們誰對這個數據有興趣就拿去吧。(本人覺得這樣理解更有趣并容易理解)由于是這個機制,所以無論你界面怎樣改變,中介器怎樣改變,都不會影響到我Proxy,因為我獲得數據時,我不用引用你某一個中介器(如果中介器改變了,則同時要修改Proxy的代碼),我只發送一個消息,你們要則要,不要則罷,所以你界面如何改變的話,只要接收這個消息就可以同樣的得到了數據,而不用因界面的修改而修改Proxy。這樣就可以將Mediator與Proxy松耦了。
至于,是誰對這個“GET_PLAYLIST_COMPLETE”感興趣呢?我在上上上面說過,Proxy只會發,不會收,所以肯定不是其它Proxy對這消息有興趣,而Command么,Command感興趣的消息都是在Facade里給綁定的,而我們上上上面的代碼中,Facade只綁定了STARTUP這個消息,所以現在唯一下來的,就是中介器了,因為中介器也是可以收與發消息的。如果一個中介器(或者說一個界面組件吧)對一個消息感興趣的話,那就需要在中介器的類里面定義這些感興趣的消息,下面我們來看看一個ControlBoardMediator的中介器的代碼:
1 package com.jiangzone.flex.pureplayer.view
2 {
3 import org.puremvc.as3.patterns.mediator.Mediator;
4 import org.puremvc.as3.interfaces.IMediator;
5 import com.jiangzone.flex.pureplayer.view.ui.SongListBoard;
6 import flash.events.MouseEvent;
7 import mx.events.ListEvent;
8 import org.puremvc.as3.interfaces.INotification;
9 import mx.controls.List;
10 import mx.collections.ArrayCollection;
11
12 public class SongListBoardMediator extends Mediator implements IMediator
13 {
14 public static const NAME:String = "SongListBoardMediator";
15
16 public function SongListBoardMediator(vc:Object):void{
17 super(NAME,vc);
18 songListBoard.songList.doubleClickEnabled = true;
19
20 songListBoard.songList.addEventListener(ListEvent.ITEM_DOUBLE_CLICK,onDoubleClick);
21 }
22
23 public function get songListBoard():SongListBoard{
24 return viewComponent as SongListBoard;
25 }
26
27 public function get songList():List{
28 return songListBoard.songList;
29 }
30
31 private function onDoubleClick(e:ListEvent):void{
32 var str:String = (e.target.selectedItem.data as String);
33 sendNotification("GET_SONG_URL_COMPLETE",str);
34 }
35
36 override public function listNotificationInterests():Array{
37 return [
38 "GET_SONG_URL",
39 "GET_PREV_URL",
40 "GET_NEXT_URL",
41 "GET_PLAYLIST_COMPLETE"
42 ];
43 }
44
45 override public function handleNotification(note:INotification):void{
46 switch(note.getName()){
47 case "GET_SONG_URL":
48 if(songList.selectedItem)
49
50
51 sendNotification("GET_SONG_URL_COMPLETE",songList.selectedItem.data as String);
52 break;
53 case "GET_PREV_URL":
54 if(songList.selectedItem){
55 var i:int = songList.selectedIndex;
56 i -= 1;
57 if(i<0) i = (songList.dataProvider as ArrayCollection).length - 1;
58 songList.selectedIndex = i;
59 sendNotification("GET_PREV_URL_COMPLETE",songList.selectedItem.data as String);
60 }
61 break;
62 case "GET_NEXT_URL":
63 if(songList.selectedItem){
64 var i:int = songList.selectedIndex;
65 i = (i + 1) % (songList.dataProvider as ArrayCollection).length;
66 songList.selectedIndex = i;
67 sendNotification("GET_PREV_URL_COMPLETE",songList.selectedItem.data as String);
68 }
69 break;
70 case "GET_PLAYLIST_COMPLETE":
71 var arr:Array = note.getBody() as Array;
72 songList.dataProvider = arr;
73 }
74 }
75 }
76 }
在這份代碼中,我們主要看看幾個方法函數,其它的都只是處理邏輯,控制Sound的播放什么的,與PureMVC關系不大了。我們主要看看這幾個方法:
override public function listNotificationInterests():Array
override public function handleNotification(note:INotification):void
先說第一個方法,這個方法返回的是一個數組,我們上面說到,怎么知道中介器對哪些消息感興趣呢?就在這個方法里設置了,這些都是接口里的方法,所以需要override,在這個方法里,直接返回一個字符串數組就可以了,而那些字符串,就是你的消息名稱 的字符串,剛才Proxy中,處理完數據后,發了一個“GET_PLAYLIST_COMPLETE”的消息,并將處理好的數據作為消息體一起發送出去了。
而在這個Mediator里的listNotificationInterests里,返回一個
1 return [
2 "GET_SONG_URL",
3 "GET_PREV_URL",
4 "GET_NEXT_URL",
5 "GET_PLAYLIST_COMPLETE"
6 ];
看到沒有?那個數組里面包含了GET_PLAYLIST_COMPLETE這個消息名稱字符串。所以,當有人發送這個消息時,這個中介器就會進行響應,至于對這個消息的響應程序,就在
override public function handleNotification(note:INotification):void
這個方法里面寫上消息響應代碼。
在響應函數里,都用switch(note.getName())來區分處理它所接收到的消息的名稱,再處以不同的處理代碼。
好了,基本上,整個PureMVC的結構就這樣了,流程是比較簡單,就是用消息(Notification)來圍繞MVC各個部分模塊來開發。這個程序還有其它的中介器類,代理類還有其它的類就不一一列出來了,反正PureMVC工作方式都是一樣的,只是對應的功能邏輯不一樣。
呼,終于全部寫完了,下面給出整個Flex Project 的 源代碼:
[down=http://www.jiangzone.com.cn/attachments/month_0806/t200864163352.zip]點擊下載此文件[/down]
posted on 2008-07-29 14:44
姜大叔 閱讀(4990)
評論(32) 編輯 收藏 所屬分類:
Flash/Flex