Pure MVC Framework 學(xué)習(xí)筆記
Posted on 2012-01-10 14:40 感冒——技術(shù) 閱讀(2226) 評(píng)論(1) 編輯 收藏 所屬分類: FlexPureMVC結(jié)構(gòu)
PureMVC框架的目標(biāo)很明確,即把程序分為低耦合的三層:Model、View和Controller。
降低模塊間的耦合性,各模塊如何結(jié)合在一起工作對(duì)于創(chuàng)建易擴(kuò)展,易維護(hù)的應(yīng)用程序是非常重要的。
在PureMVC實(shí)現(xiàn)的經(jīng)典MVC元設(shè)計(jì)模式中,這三部分由三個(gè)單例模式類管理,分別是Model、View和Controller。三者合稱為核心層或核心角色。
PureMVC中還有另外一個(gè)單例模式類——Façade,F(xiàn)açade提供了與核心層通信的唯一接口,以簡(jiǎn)化開(kāi)發(fā)復(fù)雜度。
Model 與 Proxy
Model保存對(duì)Proxy對(duì)象的引用,Proxy負(fù)責(zé)操作數(shù)據(jù)模型,與遠(yuǎn)程服務(wù)通信存取數(shù)據(jù)。
這樣保證了Model層的可移植性。
View 與 Mediator
View保存對(duì)Mediator對(duì)象的引用。由Mediator對(duì)象來(lái)操作具體的視圖組件(View Component,例如Flex的DataGrid組件),包括:添加事件監(jiān)聽(tīng)器,發(fā)送或接收Notification ,直接改變視圖組件的狀態(tài)。
這樣做實(shí)現(xiàn)了把視圖和控制它的邏輯分離開(kāi)來(lái)。
Controller 與 Command
Controller保存所有Command的映射。Command類是無(wú)狀態(tài)的,只在需要時(shí)才被創(chuàng)建。
PureMVC結(jié)構(gòu)
Controller 與 Command
Command可以獲取Proxy對(duì)象并與之交互,發(fā)送Notification,執(zhí)行其他的Command。經(jīng)常用于復(fù)雜的或系統(tǒng)范圍的操作,如應(yīng)用程序的“啟動(dòng)”和“關(guān)閉”。應(yīng)用程序的業(yè)務(wù)邏輯應(yīng)該在這里實(shí)現(xiàn)。
Façade 與 Core
Façade類應(yīng)用單例模式,它負(fù)責(zé)初始化核心層(Model,View和Controller),并能訪問(wèn)它們的Public方法。
這樣,在實(shí)際的應(yīng)用中,你只需繼承Façade類創(chuàng)建一個(gè)具體的Façade類就可以實(shí)現(xiàn)整個(gè)MVC模式,并不需要在代碼中導(dǎo)入編寫(xiě)Model,View和Controller類。
Proxy、Mediator和Command就可以通過(guò)創(chuàng)建的Façade類來(lái)相互訪問(wèn)通信。
Observer 與 Notification
PureMVC的通信并不采用Flash的EventDispatcher/Event,因?yàn)镻ureMVC可能運(yùn)行在沒(méi)有Flash Event和EventDispatcher類的環(huán)境中,它的通信是使用觀察者模式以一種松耦合的方式來(lái)實(shí)現(xiàn)的。
你可以不用關(guān)心PureMVC的Observer/Notification機(jī)制是怎么實(shí)現(xiàn)的,它已經(jīng)在框架內(nèi)部實(shí)現(xiàn)了。你只需要使用一個(gè)非常簡(jiǎn)單的方法從Proxy, Mediator, Command和Facade發(fā)送Notification,甚至不需要?jiǎng)?chuàng)建一個(gè)Notification實(shí)例。
Notification可以被用來(lái)觸發(fā)Command的執(zhí)行
Facade保存了Command與Notification之間的映射。當(dāng)Notification(通知)被
Notification可以被用來(lái)觸發(fā)Command的執(zhí)行
發(fā)出時(shí),對(duì)應(yīng)的Command(命令)就會(huì)自動(dòng)地由Controller執(zhí)行。Command實(shí)現(xiàn)復(fù)雜的交互,降低View和Model之間的耦合性。
Mediator發(fā)送、聲明、接收Notification
當(dāng)用View注冊(cè)Mediator時(shí),Mediator的listNotifications方法會(huì)被調(diào)用,以數(shù)組形式返回該Mediator對(duì)象所關(guān)心的所有Notification。
之后,當(dāng)系統(tǒng)其它角色發(fā)出同名的Notification(通知)時(shí),關(guān)心這個(gè)通知的Mediator都會(huì)調(diào)用handleNotification方法并將Notification以參數(shù)傳遞到方法。
Proxy發(fā)送,但不接收Notification
在很多場(chǎng)合下Proxy需要發(fā)送Notification(通知),比如:Proxy從遠(yuǎn)程服務(wù)接收到數(shù)據(jù)時(shí),發(fā)送Notification告訴系統(tǒng);或當(dāng)Proxy的數(shù)據(jù)被更新時(shí),發(fā)送Notification告訴系統(tǒng)。
如果讓Proxy也偵聽(tīng)Notification(通知)會(huì)導(dǎo)致它和View(視圖)層、Controller(控制)層的耦合度太高。
View和Controller必須監(jiān)聽(tīng)Proxy發(fā)送的Notification,因?yàn)樗鼈兊穆氊?zé)是通過(guò)可視化的界面使用戶能與Proxy持有的數(shù)據(jù)交互。
不過(guò)對(duì)View層和Controller層的改變不應(yīng)該影響到Model層。
例如,一個(gè)后臺(tái)管理程序和一個(gè)面向用戶程序可能共用一個(gè)Model類。如果只是用例不同,那么View/Controller通過(guò)傳遞不同的參數(shù)就可以共用相同的Model類。
MVC元設(shè)計(jì)模式的核心元素在PureMVC中體現(xiàn)為Model類、View類和Controller類。為了簡(jiǎn)化程序開(kāi)發(fā),PureMVC應(yīng)用了Façade模式。
Façade是Model、View和Controller三者的“經(jīng)紀(jì)人”。實(shí)際編寫(xiě)代碼時(shí)你并不用導(dǎo)入這三者的類文件,也不用直接使用它們。Façade類已經(jīng)在構(gòu)造方法包含了對(duì)核心MVC三者單例的構(gòu)造。
一般地,實(shí)際的應(yīng)用程序都有一個(gè)Façade子類,這個(gè)Façade類對(duì)象負(fù)責(zé)初始化Controller(控制器),建立Command與Notification名之間的映射,并執(zhí)行一個(gè)Command注冊(cè)所有的Model和View。
具體Façade是什么樣子的?
Façade類應(yīng)被當(dāng)成抽象類, 永遠(yuǎn)不被直接實(shí)例化。針對(duì)具體的應(yīng)用程序,你應(yīng)該具體編寫(xiě)Façade的子類,添加或重寫(xiě)Façade的方法來(lái)實(shí)現(xiàn)具體的應(yīng)用。按照慣例,這個(gè)類命名為“ApplicationFacade”(當(dāng)然,命名隨你喜歡),如前所述,它主要負(fù)責(zé)訪問(wèn)和通知Command,Mediator和Proxy。
通常,不同的運(yùn)行平臺(tái)都會(huì)創(chuàng)建視圖結(jié)構(gòu),盡管創(chuàng)建過(guò)程不一樣。(比如Flex中MXML程序負(fù)責(zé)實(shí)例化所有子視圖組件,F(xiàn)lash影片在Stage上構(gòu)建可視對(duì)象)。視圖結(jié)構(gòu)構(gòu)建完畢時(shí),整個(gè)PureMVC機(jī)制也已經(jīng)安置妥當(dāng)。
創(chuàng)建的Facade子類也被用來(lái)簡(jiǎn)化“啟動(dòng)”的過(guò)程。應(yīng)用程序調(diào)用Facade子類的startup方法,并傳遞自身的一個(gè)引用即完成啟動(dòng),使得應(yīng)用程序不需要過(guò)多了解PureMVC。
Façade類的內(nèi)容很簡(jiǎn)單。思考下面的例子:
ApplicationFacade.as:
package com.me.myapp
{
import org.puremvc.as3.interfaces.*;
import org.puremvc.as3..patterns.facade.*;
import com.me.myapp.view.*;
import com.me.myapp.model.*;
import com.me.myapp.controller.*;
// MyApp程序的Façade類
public class ApplicationFacade extends Façade implements IFacade
{
//定義Notification(通知)常量
public static const STARTUP:String = "startup";
public static const LOGIN:String = "login";
//得到ApplicationFacade單例的工廠方法
public static function getInstance() : ApplicationFacade
{
if ( instance == null ) instance = new ApplicationFacade( );
return instance as ApplicationFacade;
}
//注冊(cè)Command,建立Command與Notification之間的映射
override protected function initializeController( ) : void
{
super.initializeController();
registerCommand( STARTUP, StartupCommand );
registerCommand( LOGIN, LoginCommand );
registerCommand( LoginProxy.LOGIN_SUCCESS, GetPrefsCommand );
}
//啟動(dòng)PureMVC,在應(yīng)用程序中調(diào)用此方法,并傳遞應(yīng)用程序本身的引用 public function startup( app:MyApp ) : void
{
sendNotification( STARTUP, app );
}
}
}
上述代碼需要注意以下幾點(diǎn):
o
ApplicationFacade繼承自PureMVC的Façade類,F(xiàn)açade類實(shí)現(xiàn)了IFacade接口。
o
這個(gè)例子里ApplicationFacade沒(méi)有重寫(xiě)構(gòu)造方法。如果重寫(xiě)構(gòu)造方法,應(yīng)該在構(gòu)造方法里先調(diào)用父類的構(gòu)造方法。
o
類方法getInstance用于返回ApplicationFacade的單例,并將實(shí)例保存在父類的一個(gè)protect變量中。在返回該實(shí)例之前必須先把它轉(zhuǎn)化為ApplicationFacade類型。
o
o
初始化Controller(控制器),并建立Command與Notification之間的映射,當(dāng)Notification(通知)發(fā)出時(shí)相關(guān)的Command(命令)就會(huì)被執(zhí)行。
o
提供一個(gè)帶有應(yīng)用程序類型參數(shù)的startup方法,該參數(shù)能過(guò)Notification傳遞到StartupCommand。
實(shí)現(xiàn)這些只需要繼承父類很少的功能。
初始化Façade
PureMVC的Façade類在構(gòu)造方法中初始化了Model、View和Controller對(duì)象,并把對(duì)它們的引用保存在成員變量。
這樣,F(xiàn)açade就可以訪問(wèn)Model、View和Controller了。這樣把對(duì)核心層的操作都集中在Façade,避免開(kāi)發(fā)者直接操作核心層。
那么,在具體的應(yīng)用程序中,F(xiàn)açade是何時(shí)何地初始化的呢?請(qǐng)查看下面的Flex代碼:
MyApp.mxml:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
creationComplete="façade.startup(this)”>
<mx:Script>
<![CDATA[
//獲取ApplicationFacade
import com.me.myapp.ApplicationFacade;
private var facade:ApplicationFacade = ApplicationFacade.getInstance();
]]>
</mx:Script>
<!—視圖層碼的其他代-->
</mx:Application>
上面這個(gè)程序很簡(jiǎn)單:
程序運(yùn)行時(shí)創(chuàng)建視圖結(jié)構(gòu),得到ApplicationFaçade實(shí)例,調(diào)用它的startup方法。
注意:在AIR中,我們會(huì)使用“applicationComplete”代替這里的“creationComplete”;在Flash中,我們可以把對(duì)startup的調(diào)用放在第1幀或文檔類里。
請(qǐng)注意例子中以下幾點(diǎn):
o
我們使用了MXML標(biāo)簽,這種普遍的方式,創(chuàng)建程序的界面。以<mx:Application>標(biāo)簽開(kāi)始,包含組件或容器,不管是Flex內(nèi)嵌的組件還是自定義的。
o
聲明并初始化了一個(gè)私有變量,(用于)獲取ApplicationFacade單例。
o
我們初始化這個(gè)變量時(shí)使用了getInstance類方法,在應(yīng)用程序的creationComplete事件觸發(fā)時(shí),F(xiàn)acade和相關(guān)的Model,View,Controller都已經(jīng)實(shí)例化完畢(盡管此時(shí)還沒(méi)有創(chuàng)建任何Mediator和Proxy)。
o
我們?cè)贏pplication標(biāo)簽中指定了對(duì)creationComplete事件的處理過(guò)程:調(diào)用startup方法,并傳參主程序的引用。
注意:除了頂層的Application,其他視圖組件都不用(不應(yīng)該)和Façade交互。
頂層的Application(或Flash里的Movie)構(gòu)建視圖結(jié)構(gòu)、初始化Façade,然后“啟動(dòng)”整個(gè)PureMVC機(jī)制。
Notification
PureMVC使用了觀察者模式,所以各層之間能以一種松耦合的方式通信,并且與平臺(tái)無(wú)關(guān)。
ActionScript語(yǔ)言本身沒(méi)有提供flash.events包中的事件模型。況且PureMVC框架并不是只針對(duì)AS語(yǔ)言,它被移植到其他的一些平臺(tái)像C#、J2ME,所以它不會(huì)使用這些只有在Flash平臺(tái)上才有的類,它采用自己的通信機(jī)制(即Notification)。
Notification(通知)機(jī)制并不僅僅是Event(事件)機(jī)制的替代品,它們的工作方式有本質(zhì)上的不同。但這兩者相互協(xié)作可以提高視圖組件的可重用性,甚至,如果設(shè)計(jì)得當(dāng),視圖組件可以和PureMVC“脫耦”。
Event 與 Notification
Event是由實(shí)現(xiàn)IeventDispatcher接口的Flash顯示對(duì)象廣播的,Event會(huì)在整個(gè)顯示對(duì)象層中“冒泡”,這樣可以讓父級(jí)(或父級(jí)的父級(jí),等)對(duì)象處理事件。
Event機(jī)制是一個(gè)“責(zé)任鏈”的形式:除了那些可以直接引用事件發(fā)起者(dispatcher)并偵聽(tīng)它事件的對(duì)象,只有和dispatcher是父子關(guān)系的對(duì)象才會(huì)接收到事件,并對(duì)事件做出響應(yīng)動(dòng)作。
Event 與 Notification
Facade 和Proxy只能發(fā)送Notification,Mediators既可以發(fā)送也可以接收Notification,Notification被映射到 Command,同時(shí)Command也可以發(fā)送Notification。這是一種“發(fā)布/訂閱”機(jī)制,所有的觀察者都可以收到相同的通知。例如多個(gè)書(shū)刊訂閱者可以訂閱同一份雜志,當(dāng)雜志有新刊出版時(shí),所有的訂閱者都會(huì)被通知。
Notification(通知)有一個(gè)可選的“報(bào)體”,“報(bào)體”可以是任意ActionScript對(duì)象。[譯注:“報(bào)體”被用來(lái)在Notification中攜帶參數(shù),比如在上一個(gè)例子中我們發(fā)送了一個(gè)攜帶“app”參數(shù),名字叫STARTUP的Notification。]
與Flash Event不同,不需要自定義一個(gè)Notification類來(lái)傳值(注:Notification使用Object類型的"報(bào)體"來(lái)傳值)。當(dāng)然,你可以自定義Notification類以強(qiáng)類型的方式交互。這樣做的好處是編譯時(shí)有類型檢查,但卻會(huì)導(dǎo)致你必須管理很多Notification類。
Notification另有一個(gè)可選的“類型”參數(shù),用于讓接收者作為鑒別依據(jù)。
舉例,有一個(gè)文檔編輯器程序,當(dāng)一個(gè)文檔對(duì)象被打開(kāi)時(shí),程序會(huì)創(chuàng)建對(duì)應(yīng)的Proxy對(duì)象和Mediator對(duì)象(Mediator對(duì)象由視圖組件使用)。Proxy對(duì)象就可能需要在發(fā)送的Notification中加入“類型”參數(shù),以讓Mediator能夠唯一標(biāo)識(shí)文檔對(duì)象。
所有注冊(cè)了這個(gè)Proxy對(duì)象Noification的Mediator都會(huì)接收到這個(gè)Notification(通知),但它們可以根據(jù)Notification中“類型”參數(shù)決定是否做出反應(yīng)動(dòng)作。
定義Notification和Event常量
如前所述,公共的Notification名稱常量很適合定義在Façade中。所有與Notification交互的參與者都是Facade的協(xié)作者(collaborator)。
定義Notification和Event常量
當(dāng)這些Notification的名稱常量需要被其他的程序訪問(wèn)時(shí),我們可以使用單獨(dú)的“ApplicationConstants”類來(lái)存放這些Notification名稱常量定義。
不管什么時(shí)候,都應(yīng)該把Notification(通知)名稱定義為常量,需要引用一個(gè)Notification時(shí)就使用它的名稱常量,這樣做可以避免一些編譯時(shí)無(wú)法發(fā)現(xiàn)的錯(cuò)誤。因?yàn)榫幾g器可以檢查常量;而使用字符串,如果你手誤輸入錯(cuò)誤的字符串,編譯器也不法知道,也無(wú)從報(bào)錯(cuò)。
永遠(yuǎn)不要把Event的名稱定義在 Façade類里。應(yīng)該把Event名稱常量定義在那些發(fā)送事件的地方,或者就定義在Event類里。
在物理層面上審視Application,如果,View Component和Data Object在和相應(yīng)的Mediator和Proxy通信時(shí)是通過(guò)觸發(fā)Event(事件)而不是通過(guò)調(diào)用方法或發(fā)送Notification,那么View Component和Data Object就可以保持重用性。
如果一個(gè)View Component(或Data Object)觸發(fā)了一個(gè)對(duì)應(yīng)Mediator(或Proxy)正在偵聽(tīng)的Event,那么只有這一對(duì)協(xié)作者(view component-mediator,data object-proxy)需要知道事件的名稱,接下來(lái)Mediator(或Proxy)與PureMVC系統(tǒng)其他部分的通信是通過(guò)Notification進(jìn)行。
雖然協(xié)作者間(Mediator/View,或Proxy/Data)的關(guān)系是有必要緊耦合了。但“協(xié)作者對(duì)”與程序結(jié)構(gòu)的其它部分形成了松耦合,這樣在需求變更時(shí)代碼的修改范圍能有效的縮小。
Command
ApplicationFacade需要在啟動(dòng)時(shí)初始化Controller,建立Notification與Command的映射。
Controller會(huì)注冊(cè)偵聽(tīng)每一個(gè)Notification,當(dāng)被通知到時(shí),Controller會(huì)實(shí)例化一個(gè)該Notification對(duì)應(yīng)的Command類的對(duì)象。最后,將Notification作為參數(shù)傳遞給execute方法。
Command對(duì)象是無(wú)狀態(tài)的;只有在需要的時(shí)候(Controller收到相應(yīng)的Notification)才會(huì)被創(chuàng)建,并且在被執(zhí)行(調(diào)用execute方法)之后就會(huì)被刪除。所以不要在那些生命周期長(zhǎng)的對(duì)象(long-living object)里引用Command對(duì)象。
SimpleCommand和MacroCommand的使用
Command要實(shí)現(xiàn)ICommand接口。在PureMVC中有兩個(gè)類實(shí)現(xiàn)了ICommand接口:SimpleCommand、MacroCommand。
SimpleCommand只有一個(gè)execute方法,execute方法接受一個(gè)Inotification實(shí)例做為參數(shù)。實(shí)際應(yīng)用中,你只需要重寫(xiě)這個(gè)方法就行了。
MacroCommand讓你可以順序執(zhí)行多個(gè)Command。每個(gè)執(zhí)行都會(huì)創(chuàng)建一個(gè)Command對(duì)象并傳參一個(gè)對(duì)源Notification的引用。
MacroCommand在構(gòu)造方法調(diào)用自身的initializeMacroCommand方法。實(shí)際應(yīng)用中,你需重寫(xiě)這個(gè)方法,調(diào)用addSubCommand添加子Command。你可以任意組合SimpleCommand和MacroCommand成為一個(gè)新的Command。
降低Command與Mediator, Proxy的耦合度
通過(guò)發(fā)送Notification通知Controller來(lái)執(zhí)行Command,而且只能由Controller實(shí)例化并執(zhí)行Command。
為了和系統(tǒng)其他部分交互與通信,Command可能需要:
o
注冊(cè)、刪除Mediator、Proxy和Command,或者檢查它們是否已經(jīng)注冊(cè)。
降低Command與Mediator, Proxy的耦合度
o
發(fā)送Notification通知Command或Mediator做出響應(yīng)。
o
獲取Proxy和Mediator對(duì)象并直接操作它們。
Command使我們可以很容易地切換視圖元素狀態(tài),或傳送數(shù)據(jù)給它。
Command可以調(diào)用多個(gè)Proxy執(zhí)行事務(wù)處理,當(dāng)事務(wù)結(jié)束后,發(fā)送Notification或處理異常和失敗。
復(fù)雜的操作與業(yè)務(wù)邏輯
在程序的很多地方你都可以放置代碼(Command,Mediator和Proxy);不可避免地會(huì)不斷碰到一個(gè)問(wèn)題:
哪些代碼應(yīng)該放在哪里?確切的說(shuō),Command應(yīng)該做什么?
程序中的邏輯分為Business Logic(業(yè)務(wù)邏輯)和Domain Logic(域邏輯),首先需要知道這兩者之間的差別。
Command管理應(yīng)用程序的Business Logic(業(yè)務(wù)邏輯),與Domain Logic(域邏輯)相區(qū)別,Business Logic(業(yè)務(wù)邏輯)要協(xié)調(diào)Model與視圖狀態(tài)。
Model通過(guò)使用Proxy來(lái)保證數(shù)據(jù)的完整性、一致性。Proxy集中程序的Domain Logic(域邏輯),并對(duì)外公布操作數(shù)據(jù)對(duì)象的API。它封裝了所有對(duì)數(shù)據(jù)模型的操作,不管數(shù)據(jù)是客戶端還是服務(wù)器端的,對(duì)程序其他部分來(lái)說(shuō)就是數(shù)據(jù)的訪問(wèn)是同步還是異步的。
Command可能被用于實(shí)現(xiàn)一些復(fù)雜、必須按照一定順序的系統(tǒng)行為,上一步動(dòng)作的結(jié)果可能會(huì)流入一下個(gè)動(dòng)作。.
Mediator和Proxy可以提供一些操作接口讓Command調(diào)用來(lái)管理View Component和Data Object,同時(shí)對(duì)Command隱藏具體操作的細(xì)節(jié)。
復(fù)雜的操作與業(yè)務(wù)邏輯
我們這里談?wù)摰腣iew Component時(shí)就是指像按鈕這種用戶直接交互的小東西。而Data Object則是指能以任意結(jié)構(gòu)存儲(chǔ)數(shù)據(jù)的對(duì)象。
Command與Mediator和Proxy交互,應(yīng)避免Mediator與Proxy直接交互。請(qǐng)看下面這個(gè)用于程序“啟動(dòng)”的Command:
StartupCommand.as:
package com.me.myapp.controller
{
import org.puremvc.as3.interfaces.*;
import org.puremvc.as3.patterns.command.*;
import com.me.myapp.controller.*;
// 開(kāi)時(shí)執(zhí)程序始行的MacroCommand.
public class StartupCommand extends MacroCommand
{
//添加子Command初始化MacroCommand.
override protected function initializeMacroCommand() : void
{
addSubCommand( ModelPrepCommand );
addSubCommand( ViewPrepCommand );
}
}
}
這是一個(gè)添加了兩個(gè)子Command的MacroCommand,執(zhí)行時(shí)兩個(gè)版子命令會(huì)按照“先進(jìn)先出”(FIFO)的順序被執(zhí)行。
復(fù)雜的操作與業(yè)務(wù)邏輯
這個(gè)復(fù)合命令定義了PureMVC在“開(kāi)啟”(startup)時(shí)的動(dòng)作序列。但具體的,我們應(yīng)該做什么?按照什么順序?
在用戶與數(shù)據(jù)交互之前,Model必須處于一種一致的已知的狀態(tài)。一旦Model初始化完成,視圖就可以顯示數(shù)據(jù)允許用戶操作與之交互。
因此,一般“開(kāi)啟”(startup)過(guò)程有兩個(gè)主要的動(dòng)作:Model初始化與View初始化。
ModelPrepCommand.as:
package com.me.myapp.controller
{
import org.puremvc.as3.interfaces.*;
import org.puremvc.as3.patterns.observer.*;
import org.puremvc.as3.patterns.command.*;
import com.me.myapp.*;
import com.me.myapp.model.*;
//創(chuàng)建Proxy對(duì)象,并注冊(cè)。
public class ModelPrepCommand extends SimpleCommand
{
//由MacroCommand調(diào)用
override public function execute( note : INotification ) : void
{
facade.registerProxy( new SearchProxy() );
facade.registerProxy( new PrefsProxy() );
facade.registerProxy( new UsersProxy() );
}
}
}
Model的初始化通常比較簡(jiǎn)單:創(chuàng)建并注冊(cè)在“開(kāi)啟”過(guò)程中需要用到的Proxy。
上面這個(gè)ModelPrepCommand類是一個(gè)SimpleCommand例子,它功能就是初始化Model,它是前面那個(gè)MacroCommand的第一個(gè)子命令,所以它會(huì)最先被執(zhí)行。
通常具體的Façade對(duì)象,它創(chuàng)建并注冊(cè)了多個(gè)在“啟動(dòng)”(startup)過(guò)程中會(huì)用到的Proxy類。注意這里Command并沒(méi)有操作或初始任何的Model數(shù)據(jù)。Proxy的職責(zé)才是取得,創(chuàng)建,和初始化數(shù)據(jù)對(duì)象。
ViewPrepCommand.as:
package com.me.myapp.controller
{
import org.puremvc.as3.interfaces.*;
import org.puremvc.as3.patterns.observer.*;
import org.puremvc.as3.patterns.command.*;
import com.me.myapp.*;
import com.me.myapp.view.*;
//創(chuàng)建Mediator們,并把它注冊(cè)到View.
public class ViewPrepCommand extends SimpleCommand
{
override public function execute( note : INotification ) : void {
var app:MyApp = note.getBody() as MyApp;
facade.registerMediator( new ApplicationMediator( app ) );
}
}
}
這個(gè)SimpleCommand的功能是初始化View。它是之前那個(gè)MacroCommand的最后一個(gè)子命令,所以它會(huì)被最后一個(gè)執(zhí)行。
注意這個(gè)命令唯一創(chuàng)建并注冊(cè)的Mediator是ApplicationMediator,ApplicationMediator被用于操作Appliction View。
更深入一點(diǎn):它向創(chuàng)建Mediator對(duì)象時(shí),向Mediator構(gòu)造函數(shù)傳參Notification的“報(bào)體”。這個(gè)“報(bào)體”是個(gè)對(duì)Application的引用,它是在最初的“啟動(dòng)通知”(STARTUP Notification)發(fā)出時(shí)由Application自身發(fā)送的。(請(qǐng)查看之前的MyApp范例。)
Application是個(gè)有點(diǎn)特殊的視圖組件(View Component),它包含其他所有視圖組件(View Component)并在啟動(dòng)時(shí)創(chuàng)建它們。
為了和系統(tǒng)其他部分通信,視圖組件需要使用Mediator。創(chuàng)建Mediator對(duì)象需要引用此Mediaotr管理的視圖組件(View component)的引用,這些Mediator和View Component的對(duì)應(yīng)關(guān)系只有Application知道。
比較特殊的是Application的Mediator,它是唯一的被允許知道Application一切的類,所以我們會(huì)在Application Mediator的構(gòu)造函數(shù)中創(chuàng)建其他的Mediator對(duì)象。
上面的三個(gè)Command初始化了Model和View。這樣做Command不需要知道太多Model和View的細(xì)節(jié)。
當(dāng)Model或View的實(shí)現(xiàn)發(fā)生改變時(shí)就只需要改變Model和View和相應(yīng)部分即可,而不需要改變Command的邏輯。
Command的業(yè)務(wù)邏輯應(yīng)該避免被Model或View的變化而受到影響。
復(fù)雜的操作與業(yè)務(wù)邏輯
Model應(yīng)該邏輯封裝域(domain logic)證,保數(shù)據(jù)的完整性。Command則處理事務(wù)業(yè)務(wù)邏輯協(xié)調(diào)或,多個(gè)Proxy處的操作,理異常等。
Mediator
Mediator是視圖組件(View Component,例如Flex的DataGrid或Flash的
MovieClip)與系統(tǒng)其他部分交互的中介器。
在基于Flash的應(yīng)用程序中,Mediator 偵聽(tīng)View Component來(lái)處理用戶動(dòng)作和Component的數(shù)據(jù)請(qǐng)求。Mediator通過(guò)發(fā)送和接收Notification來(lái)與程序其他部分通信。
Mediator的職責(zé)
Flash、Flex和AIR框架都提供了豐富強(qiáng)大的交互UI組件。你可以擴(kuò)展這些組件或者編寫(xiě)自己的組件為用戶提供更強(qiáng)大的交互方式和數(shù)據(jù)呈現(xiàn)方式。
在不遠(yuǎn)的將來(lái),會(huì)有越來(lái)越多的平臺(tái)運(yùn)行ActionScript語(yǔ)言。PureMVC也已經(jīng)有移植到其他平臺(tái)上,包括Silverlight和J2ME,拓寬了PureMVC技術(shù)的RIA開(kāi)發(fā)道路。
PureMVC的目標(biāo)之一就是保持平臺(tái)無(wú)關(guān)性,不管你使用什么技術(shù)什么UI組件什么數(shù)據(jù)結(jié)構(gòu)都能應(yīng)用PureMVC框架。
對(duì)基于PureMVC的應(yīng)用程序來(lái)說(shuō),View Component可以是任意的UI Component,不用管所處的框架是什么(Flex,Java還是C#),也不用管它有多少個(gè)組件。一個(gè)View Component應(yīng)該把盡可能自己的狀態(tài)和操作封裝起來(lái),對(duì)外只提供事件、方法和屬性的簡(jiǎn)單的API。
Mediator保存了一個(gè)或多個(gè)View Component的引用,通過(guò)View Component自身提供的API管理它們。
Mediator的主要職責(zé)是處理View Component派發(fā)的事件和系統(tǒng)其他部分發(fā)出來(lái)的Notification(通知)。
因?yàn)镸ediator也會(huì)經(jīng)常和Proxy交互,所以經(jīng)常在Mediator的構(gòu)造方法中取得Proxy實(shí)例的引用并保存在Mediator的屬性中,這樣避免頻繁的獲取Proxy實(shí)例。
轉(zhuǎn)化View Component類型
PureMVC的Mediator基類在構(gòu)造方法中提供兩個(gè)參數(shù):name(名稱)和一個(gè)Object類型的對(duì)象。
這個(gè)Mediator子類會(huì)在構(gòu)造函數(shù)中把它的View Component傳參給父類,它會(huì)在內(nèi)部賦值給一個(gè)protect屬性:viewComponent,并傳化為Object類型。
在Mediator被構(gòu)造之后,你可能通過(guò)調(diào)用它的setViewComponent函數(shù)來(lái)動(dòng)態(tài)給它的View Component賦值(修改)。
之后,每一次需要訪問(wèn)這個(gè)Object的API時(shí),你都要手動(dòng)把這個(gè)Object轉(zhuǎn)化成它的真正類型。這是一項(xiàng)煩瑣重復(fù)的工作。
ActionScript語(yǔ)言提供隱式setter和getter。隱式的setter和getter看起來(lái)像方法,但對(duì)外是屬性。實(shí)踐證明setter和getter對(duì)解決頻繁轉(zhuǎn)換類型問(wèn)題是很好的解決方法。
一種常用做法是在具體的Mediator中提供一個(gè)隱式getter來(lái)對(duì)View Component進(jìn)行類型轉(zhuǎn)換,注意給這個(gè)getter命名一個(gè)合適的名字。
示例:
protected function get controlBar() : MyAppControlBar
{
return viewComponent as MyAppControlBar;
}
之后,在Mediator的其他地方,我們不再這樣做了:
MyAppControlBar ( viewComponent ).searchSelction = MyAppControlBar.NONE_SELECTED;
我們會(huì)這樣:
controlBar.searchSelction = MyAppControlBar.NONE_SELECTED;
監(jiān)應(yīng)聽(tīng)并響View Component
通常一個(gè)Mediator只對(duì)應(yīng)一個(gè)View Component,但卻可能需要管理多個(gè)UI控件,比如一個(gè)ApplicationToolBar和它包含的button或control。我們可以把一組相關(guān)的Control(比如from)放在一個(gè)View Component里,把這組相關(guān)的控件當(dāng)作一個(gè)View Component,對(duì)Mediator來(lái)說(shuō),這些控件是這個(gè)View Component的屬性,應(yīng)盡可能的封裝它們的操作與之交互。
Mediator負(fù)責(zé)處理與Controller層、Model層交互,在收到相關(guān)Notification時(shí)更新View Component。
在Flash平臺(tái)上,一般在構(gòu)造方法中或setViewComponent方法被調(diào)用后,給View Component添加事件監(jiān)聽(tīng)器。
controlBar.addEventListener( AppControlBar.BEGIN_SEARCH, onBeginSearch );
監(jiān)應(yīng)聽(tīng)并響View Component
Mediator按需求對(duì)事件做出響應(yīng)。
一般地,一個(gè)Mediator的事件響應(yīng)會(huì)有以下幾種處理:
o
檢查事件類型或事件的自定義內(nèi)容。
o
檢查或修改View Component的屬性(或調(diào)用提供的方法)。
o
檢查或修改Proxy對(duì)象公布的屬性(或調(diào)用提供的方法)。
o
發(fā)送一個(gè)或多個(gè)Notification,通知?jiǎng)e的Mediatora或Command作出響應(yīng)(甚至有可能發(fā)送給自身)。
下面是一些有用的經(jīng)驗(yàn):
o
如果有多個(gè)的Mediator對(duì)同一個(gè)事件做出響應(yīng),那么應(yīng)該發(fā)送一個(gè)Notification,然后相關(guān)的Mediator做出各自的響應(yīng)。
o
如果一個(gè)Mediator需要和其他的Mediator進(jìn)行大量的交互,那么一個(gè)好方法是利用Command把交互步驟定義在一個(gè)地方。
o
不應(yīng)該讓一個(gè)Mediator直接去獲取調(diào)用其他的Mediator,在Mediator中定義這樣的操作本身就是錯(cuò)誤的。
o
Proxy是有狀態(tài)的,當(dāng)狀態(tài)發(fā)生變化時(shí)發(fā)送Notification通知Mediator,將數(shù)據(jù)的變化反映到視圖。
與給視圖添加嚴(yán)格的事件監(jiān)聽(tīng)器相比,Mediator與PureMVC系統(tǒng)的其它部分關(guān)聯(lián)起來(lái)是簡(jiǎn)單而且自動(dòng)化的。
在 Mediator實(shí)例化時(shí),PureMVC會(huì)調(diào)用Mediator的listNotificationInterests方法查詢其關(guān)心的 Notification,Mediator則在listNotificationInterests方法中以數(shù)據(jù)形式返回這些Notification 名稱。
最簡(jiǎn)單的方法是返回一個(gè)由Notification名稱組成的匿名數(shù)組。前面提過(guò),Notification名稱通常以常量形式定義在Façade類中。
下面是個(gè)例子:
override public function listNotificationInterests() : Array
{
return [
ApplicationFacade.SEARCH_FAILED,
ApplicationFacade.SEARCH_SUCCESS
];
}
當(dāng)這個(gè)數(shù)組里的一個(gè)Notification被系統(tǒng)的其他部分(也可能是Mediator對(duì)象自身)發(fā)出時(shí),Mediator對(duì)象的handleNotification函數(shù)會(huì)被調(diào)用,并傳進(jìn)Notification參數(shù)。
在handleNotification函數(shù)里,使用的是“switch/case”而不是“if/else if”的分支控制,因?yàn)榍罢吒鬃x,而且增加、刪除一個(gè)Notification比較方便。
本質(zhì)上來(lái)講,在收到一個(gè)Notification時(shí)Mediator是所要操作的是很少的。有時(shí)候(偶爾),我們需要從Notification里獲取有關(guān)Proxy的信息,但記住,不應(yīng)該讓處理Notification的方法負(fù)責(zé)復(fù)雜邏輯。業(yè)務(wù)邏輯應(yīng)該放在Command中而非在Mediator中。
在Mediator里處理Notification
override public function handleNotification( note : INotification ) : void
{
switch ( note.getName() )
{
case ApplicationFacade.SEARCH_FAILED:
controlBar.status = AppControlBar.STATUS_FAILED;
controlBar.searchText.setFocus();
break;
case ApplicationFacade.SEARCH_SUCCESS:
controlBar.status = AppControlBar.STATUS_SUCCESS;
break;
}
}
}
還有,一般一個(gè)Mediator(handleNotification方法)處理的Notification應(yīng)該在4、5個(gè)之內(nèi)。
還要注意的是,Mediator的職責(zé)應(yīng)該要細(xì)分。如果處理的Notification很多,則意味著Mediator需要被拆分,在拆分后的子模塊的Mediator里處理要比全部放在一起更好。
通常對(duì)于每一個(gè)事件都需要添加一個(gè)監(jiān)聽(tīng)方法,一般這些方法只是發(fā)送Notification,處理邏輯應(yīng)該不復(fù)雜,也不應(yīng)該涉及太多的View Component細(xì)節(jié),因?yàn)閷?duì)視圖View Component的處理應(yīng)該盡可能的封裝起來(lái)。
對(duì)于Notification,Mediator有唯一的一個(gè)處理方法,這個(gè)方法中它會(huì)(通過(guò)switch/case)處理所有的Notification。
最好是把對(duì)所有Notification的處理放在handleNotification方法中,使用switch/case來(lái)區(qū)分Notification名稱。
已經(jīng)有很多關(guān)于“switch/case”用法的爭(zhēng)論,很多開(kāi)發(fā)者認(rèn)為它有局限性,因?yàn)樗械那闆r都在一個(gè)函數(shù)里處理了。不過(guò),單一的Notification處理函數(shù)以及“switch/case”分支風(fēng)格是PureMVC特意選定的。這樣做可以限定Mediator的代碼量,并讓代碼保持PureMVC推薦的結(jié)構(gòu)。
Mediator是被用來(lái)負(fù)責(zé)View Component和系統(tǒng)其他部分的通信的。
就像一個(gè)翻譯員在聯(lián)合國(guó)大會(huì)(UN conference)上給大使們翻譯對(duì)話。她應(yīng)當(dāng)盡量少做翻譯(轉(zhuǎn)發(fā)信息)之外的事情,偶爾可以引用一下比喻、事實(shí)。Mediator在PureMVC中也是這樣的一個(gè)角色。
Mediator和Proxy之間、Mediator和其他Mediator之間的耦合
View本質(zhì)上是顯示Model的數(shù)據(jù)并讓用戶能與之交互,我們期望一種單向依賴,即View依賴于Model,而Model卻不依賴于View。View必須知道Model的數(shù)據(jù)是什么,但Model卻并不需要知道View的任何內(nèi)容。
雖然Mediator可以任意訪問(wèn)Proxy,通過(guò)Proxy的API讀取、操作Data Object,但是,由Command來(lái)做這些工作可以實(shí)現(xiàn)View和Model之間的松耦合。
同樣的情況,雖然Mediator可以從View獲取其他的Mediator,通過(guò)API訪問(wèn)、操作它們。但這樣是很不好的,它會(huì)導(dǎo)致View下成員的相互依賴,這違反了“改變一個(gè)不影響其他”的目的。
如果一個(gè)Mediator要和其他Mediator通信,那它應(yīng)該發(fā)送Notification來(lái)實(shí)現(xiàn),而不是直接引用這個(gè)Mediator來(lái)操作。
Mediator對(duì)外不應(yīng)該公布操作View Component的函數(shù)。而是自己接收Notification做出響應(yīng)來(lái)實(shí)現(xiàn)。
如果在Mediator里有很多對(duì)View Component的操作(響應(yīng)Event或Notification),那么應(yīng)該考慮將這些操作封裝為View Component的一個(gè)方法,提高可重用性。
如果一個(gè)Mediator有太多的對(duì)Proxy及其數(shù)據(jù)的操作,那么,應(yīng)該把這些代碼重構(gòu)在Command內(nèi),簡(jiǎn)化Mediator,把業(yè)務(wù)邏輯(Business Logic)移放到Command上,這樣Command可以被View的其他部分重用,還會(huì)實(shí)現(xiàn)View和Model之間的松耦合提高擴(kuò)展性。
用戶與View Component和Mediator的交互
假如有一個(gè)含表單的LoginPanel組件。對(duì)應(yīng)有一個(gè)LoginPanelMediator,負(fù)責(zé)與LoginPanel交互并響應(yīng)它的輸入信息發(fā)送登錄請(qǐng)求。
LoginPanel和LoginPanelMediator之間的協(xié)作表現(xiàn)為:LoginPanel在用戶輸入完信息要登錄時(shí)發(fā)送一個(gè)TRY_LOGIN的事件,LoginPanelMediator處理這個(gè)事件,處理方法是發(fā)送一個(gè)以組件包含的LoginVO為“報(bào)體”的Notification(通知)。
LoginPanel.mxml:
<?xml version="1.0" encoding="utf-8"?>
<mx:Panel xmlns:mx="http://www.adobe.com/2006/mxml"
title="Login" status=”{loginStatus}”>
<!—
The events this component dispatches. Unfortunately we can’t use
the constant name here, because MetaData is a compiler directive
-->
<mx:MetaData>
[Event('tryLogin')];
</mx:MetaData>
<mx:Script>
<![CDATA[
import com.me.myapp.model.vo.LoginVO;
// 表單項(xiàng)與LoginVO對(duì)象的屬性雙向綁定。
[Bindable] public var loginVO:LoginVO = new LoginVO();
[Bindable] public var loginStatus:String = NOT_LOGGED_IN;
//義定Event名稱常量
public static const TRY_LOGIN:String='tryLogin';
public static const LOGGED_IN:String='Logged In';
public static const NOT_LOGGED_IN:String='Enter Credentials';
]]>
</mx:Script>
<mx:Binding source="username.text" destination="loginVO.username"/>
<mx:Binding source="password.text" destination="loginVO.password"/>
用戶與View Component和Mediator的交互
<!—The Login Form -->
<mx:Form id="loginForm" >
<mx:FormItem label="Username:">
<mx:TextInput id="username" text="{loginVO.username}" />
</mx:FormItem>
<mx:FormItem label="Password:">
<mx:TextInput id="password" text="{loginVO.password}"
displayAsPassword="true" />
</mx:FormItem>
<mx:FormItem >
<mx:Button label="Login" enabled="{loginStatus == NOT_LOGGED_IN}”
click="dispatchEvent( new Event(TRY_LOGIN, true ));"/>
</mx:FormItem>
</mx:Form>
</mx:Panel>
LoginPanel組件包含有一個(gè)用用戶表單輸入新創(chuàng)建的LoginVO對(duì)象,當(dāng)用戶單擊“Login”按鈕時(shí)觸發(fā)一個(gè)事件,接下來(lái)的事情由LoginPanelMediator接管。
這樣View Component的角色就是簡(jiǎn)單收集數(shù)據(jù),收集完數(shù)據(jù)通知系統(tǒng)。
可以完善的地方是只有當(dāng)username和password都有內(nèi)容時(shí)才讓login按鈕可用(enable),這樣可以避免惡意登錄。
View Component對(duì)外隱藏自己的內(nèi)部實(shí)現(xiàn),它由Mediator使用的整個(gè)API包括:一個(gè)TRY_LOGIN事件,一個(gè)LoginVO屬性和Panel的狀態(tài)屬性。
LoginPanelMediator會(huì)對(duì)LOGIN_FAILED和LOGIN_SUCCESS通知做出反應(yīng),設(shè)置LoginPanel的狀態(tài)。
LoginPanelMediator.as:
package com.me.myapp.view
{
import flash.events.Event;
import org.puremvc.as3.interfaces.*;
import org.puremvc.as3.patterns.mediator.Mediator;
import com.me.myapp.model.LoginProxy;
import com.me.myapp.model.vo.LoginVO;
import com.me.myapp.ApplicationFacade;
import com.me.myapp.view.components.LoginPanel;
// LoginPanel視圖的Mediator
public class LoginPanelMediator extends Mediator implements IMediator
{
public static const NAME:String = 'LoginPanelMediator';
public function LoginPanelMediator( viewComponent:LoginPanel )
{
super( NAME, viewComponent );
LoginPanel.addEventListener( LoginPanel.TRY_LOGIN, onTryLogin );
}
// 列出該Mediator關(guān)心的Notification
override public function listNotificationInterests( ) : Array
{
return [
LoginProxy.LOGIN_FAILED,
LoginProxy.LOGIN_SUCCESS
];
}
// 處理Notification
override public function handleNotification( note:INotification ):void
{
switch ( note.getName() ) {
case LoginProxy.LOGIN_FAILED:
LoginPanel.loginVO = new LoginVO( );
loginPanel.loginStatus = LoginPanel.NOT_LOGGED_IN;
break;
case LoginProxy.LOGIN_SUCCESS:
loginPanel.loginStatus = LoginPanel.LOGGED_IN;
break;
}
}
// 戶單擊用Login鈕嘗試錄按,登。
private function onTryLogin ( event:Event ) : void {
sendNotification( ApplicationFacade.LOGIN, loginPanel.loginVO );
}
// 把viewComponent轉(zhuǎn)類化成它真正的型。
protected function get loginPanel() : LoginPanel {
return viewComponent as LoginPanel;
}
}
}
注意LoginPanelMediator在構(gòu)造方法中給LoginPanel注冊(cè)了一個(gè)偵聽(tīng)方法——onTryLogin,當(dāng)用戶單擊Login按鈕時(shí)這個(gè)方法會(huì)被執(zhí)行。在onTryLogin方法里發(fā)送了一個(gè)LOGIN的Notification(通知,攜帶參數(shù)LoginVO對(duì)象)。
早先(在ApplicationFacade中)我們已經(jīng)把LoginCommand注冊(cè)到這個(gè)Notification上了。LoginCommand會(huì)調(diào)用LoginProxy的“登錄”方法,傳參LoginVO。LoginProxy把“登錄”請(qǐng)求遠(yuǎn)程服務(wù),之后發(fā)送LOGIN_SUCCESS(登錄成功)或LOGIN_FAILED(登錄失?。┑腘otification。這些類的定義請(qǐng)參見(jiàn)“Proxy”章節(jié)。
LoginPanelMediator把LOGIN_SUCCESS和LOGIN_FAILED注冊(cè)自己的關(guān)心的Notification,當(dāng)這兩個(gè)Notification被發(fā)送時(shí),Mediaotr作為響應(yīng)把LoginPanel的loginStatus設(shè)置為L(zhǎng)OGGED_IN(登錄成功時(shí))或NOT_LOGGED_IN(登錄失敗時(shí)),并清除LoginVO對(duì)象。
Proxy
一般來(lái)說(shuō),Proxy Pattern(代理模式)被用來(lái)為控制、訪問(wèn)對(duì)象提供一個(gè)代理。在基于PureMVC的應(yīng)用程序,Proxy類被設(shè)計(jì)用來(lái)管理程序數(shù)據(jù)模型。
一個(gè)Proxy有可能管理對(duì)本地創(chuàng)建的數(shù)據(jù)結(jié)構(gòu)的訪問(wèn)。它是Proxy的數(shù)據(jù)對(duì)象。
在這種情況下,通常會(huì)以同步的方式取得或設(shè)置數(shù)據(jù)。Proxy可能會(huì)提供訪問(wèn)Data Object部分屬性或方法的API,也可能直接提供Data Object的引用。如果提供了更新Data Object的方法,那么在數(shù)據(jù)被修改時(shí)可能會(huì)發(fā)送一個(gè)Notifidation通知系統(tǒng)的其它部分。
Remote Proxy被用來(lái)封裝與遠(yuǎn)程服務(wù)的數(shù)據(jù)訪問(wèn)。Proxy維護(hù)那些與Remote service(遠(yuǎn)程服務(wù))通信的對(duì)象,并控制對(duì)這些數(shù)據(jù)的訪問(wèn)。
在這種情況下,調(diào)用Proxy獲取數(shù)據(jù)的方法,然后等待Proxy在收到遠(yuǎn)程服務(wù)的數(shù)據(jù)后發(fā)出異步Notification。
Proxy封裝了數(shù)據(jù)模型,管理Data Object及對(duì)Data Object的訪問(wèn),不管數(shù)據(jù)來(lái)自哪里,什么類型。
在PureMVC中,Proxy是個(gè)被Model注冊(cè)的簡(jiǎn)單的數(shù)據(jù)持有者。
雖然Proxy類已經(jīng)是完全可用的了,但是通常對(duì)于具體的應(yīng)用你應(yīng)該編寫(xiě)Proxy的子類,增加操作方法。
通常Proxy Pattern有以下幾種類型:
o
Remote Proxy, 當(dāng)Proxy管理的數(shù)據(jù)存放在遠(yuǎn)程終端,通過(guò)某種服務(wù)訪問(wèn)。
o
Proxy and Delegate, 多個(gè)Proxy共享對(duì)一個(gè)服務(wù)的訪問(wèn),由Delegate封裝對(duì)服務(wù)的控制訪問(wèn),確保響應(yīng)正確的返回給相應(yīng)的請(qǐng)求者。
o
Protection Proxy, 用于數(shù)據(jù)對(duì)象的訪問(wèn)有不同的權(quán)限時(shí)。
o
Virtual Proxy, 對(duì)創(chuàng)建開(kāi)銷很大的數(shù)據(jù)對(duì)象進(jìn)行管理。
o
Smart Proxy, 首次訪問(wèn)時(shí)載入數(shù)據(jù)對(duì)象到內(nèi)存,并計(jì)算它被引用的次數(shù),允許鎖定確保其他對(duì)象不能修改。
轉(zhuǎn)換數(shù)據(jù)對(duì)象
Proxy基類的構(gòu)造方法接受一個(gè)名稱(name)和一個(gè)Object類型的參數(shù),Object類型的參數(shù)用來(lái)設(shè)置Proxy管理的數(shù)據(jù)模型,在構(gòu)造方法完成后也可以調(diào)用
轉(zhuǎn)換數(shù)據(jù)對(duì)象
setData方法來(lái)設(shè)置。
就像Mediator和它的View Component一樣,為了訪問(wèn)它的屬性和方法,你會(huì)經(jīng)常需要把這個(gè)Data Object轉(zhuǎn)化成它真正的類型??雌饋?lái)只是重復(fù)繁瑣了一些,但更嚴(yán)重的是可能會(huì)暴露過(guò)多的Data Object細(xì)節(jié)。
另外,因?yàn)镈ata Object通常是一個(gè)復(fù)雜的數(shù)據(jù)結(jié)構(gòu),我們經(jīng)常需要引用它的一部分屬性并將類型轉(zhuǎn)化成我們需要的數(shù)據(jù)。
再一次的,ActionScript語(yǔ)言支持隱式的getter和setter屬性,它可以很好地幫助我們解決這種頻繁的類型轉(zhuǎn)換問(wèn)題。
一個(gè)很好的慣用做法是在你具體的Proxy類中引入一個(gè)適當(dāng)命名的隱式getter,用來(lái)把Data Object轉(zhuǎn)化它真正的類型。
另外,可能需要定義不同的多個(gè)類型getter來(lái)取得Data Object某部分的數(shù)據(jù)。
比如:
public function get searchResultAC () : ArrayCollection
{
return data as ArrayCollection;
}
public function get resultEntry( index:int ) : SearchResultVO
{
return searchResultAC.getItemAt( index ) as SearchResultVO;
}
在別的Mediator中,我們不用再這樣做了:
var item:SearchResultVO =
ArrayCollection ( searchProxy.getData() ).lastResult.getItemAt( 1 ) as SearchResultVO;
而可以這樣:
var item:SearchResultVO = searchProxy.resultEntry( 1 );
避免對(duì)Mediator的依賴
Proxy不監(jiān)聽(tīng)Notification,也永遠(yuǎn)不會(huì)被通知,因?yàn)镻roxy并不關(guān)心View的狀態(tài)。但是,Proxy提供方法和屬性讓其它角色更新數(shù)據(jù)。
Proxy對(duì)象不應(yīng)該通過(guò)引用、操作Mediator對(duì)象來(lái)通知系統(tǒng)它的Data Object(數(shù)據(jù)對(duì)象)發(fā)生了改變。
它應(yīng)該采取的方式是發(fā)送Notification(這些Notification可能被Command或Mediator響應(yīng))。Proxy不關(guān)心這些Notification被發(fā)出后會(huì)影響到系統(tǒng)的什么。
把Model層和系統(tǒng)操作隔離開(kāi)來(lái),這樣當(dāng)View層和Controller層被重構(gòu)時(shí)就不會(huì)影響到Model層。
但反過(guò)來(lái)就不是這樣了:Model層的改變很難不影響到View層和Controller層。畢竟,它們存在的目的就是讓用戶與Model層交互的。
Model層中的改變總會(huì)造成View/Controller層的一些重構(gòu)。
我們把Domain Logic(域邏輯)盡可能放在Proxy中實(shí)現(xiàn),這樣盡可能地做到Model層與相關(guān)聯(lián)的View層、Controller層的分離。
Proxy僅僅對(duì)訪問(wèn)對(duì)不用來(lái)管理數(shù)據(jù)象的,而且用來(lái)封裝數(shù)據(jù)象的操作使得維態(tài)數(shù)據(jù)持在一個(gè)合法的狀。
比如,算稅是一個(gè)計(jì)營(yíng)業(yè)(域邏輯),它放在應(yīng)該中Domain LogicProxy實(shí)現(xiàn)而不是Mediator或Command。
雖然可以放在任意一個(gè)中實(shí)現(xiàn),但是把它放在Proxy中實(shí)現(xiàn)不僅僅是出于邏輯(logic)上的考慮,這樣做還可以保持其它層更輕便、更易被重構(gòu)。
一個(gè)Mdeiator可能獲取(retrieve)這個(gè)Proxy對(duì)象;調(diào)用它的營(yíng)業(yè)稅計(jì)算方法,傳參一些表單項(xiàng)目數(shù)據(jù)。如果把真正的計(jì)算放在Mediator的話,就是把Domain Logic(域邏輯)嵌在View層了。對(duì)營(yíng)業(yè)稅的計(jì)算是Domain Model(域模型)中的一條規(guī)則。View僅僅是把它看成Domain Model的一個(gè)屬性,當(dāng)用戶的輸入正確這個(gè)屬性對(duì)View就可用。
假設(shè)你現(xiàn)在正在工作的程序項(xiàng)目是一個(gè)嵌在瀏覽器中桌面級(jí)的RIA解決方案。但新的版本可能是簡(jiǎn)化了用例(use case)的嵌在PDA中的解決方案,但仍然完全需要當(dāng)前程序項(xiàng)目的Model層。
如果已經(jīng)有正確的分離操作,我們就可以完全重用Model層而只需開(kāi)發(fā)新的View層和Controller層。
雖然把對(duì)營(yíng)業(yè)稅的計(jì)算放在Mediator上看起來(lái)很有效而且在寫(xiě)代碼時(shí)也很容易;你可能只需要把營(yíng)業(yè)稅計(jì)算出來(lái)交給Model而已。
然而你卻需要在程序的不同版本中重復(fù)的付出,在每個(gè)新的View層復(fù)制粘貼營(yíng)業(yè)稅計(jì)算邏輯,所以最好把這段邏輯放在Model層。
Remote Proxy對(duì)象是一個(gè)從遠(yuǎn)程位置(Remote location)獲取Data Object的Proxy。這通常意味著我們與它的交互是以異步的方式。
Proxy獲取數(shù)據(jù)的方式取決于客戶端平臺(tái)、遠(yuǎn)程服務(wù)(remote service)的實(shí)現(xiàn)和開(kāi)發(fā)人員的選擇。在Flash/Flex環(huán)境中,我們可能會(huì)使用HTTPService,WebService,RemoteObject,DataService或者XMLSocket來(lái)從Proxy中發(fā)送服務(wù)請(qǐng)求。
根據(jù)需要,Remote Proxy可能動(dòng)態(tài)的發(fā)送請(qǐng)求,響應(yīng)時(shí)會(huì)設(shè)置一個(gè)屬性或調(diào)用一個(gè)方法; 或只在構(gòu)造方法中發(fā)送一次請(qǐng)求,然后提供訪問(wèn)數(shù)據(jù)的get/set方法。
在Proxy中有很多東西可以優(yōu)化以提高與遠(yuǎn)程服務(wù)通信的效率。
比如:緩存(服務(wù)器返回的)數(shù)據(jù)以減少網(wǎng)絡(luò)通信的“廢話”;只發(fā)送改變的數(shù)據(jù),減少帶寬的浪費(fèi)。
如果請(qǐng)求是由系統(tǒng)其它角色動(dòng)態(tài)調(diào)用Remote Proxy的方法而發(fā)出的,那Proxy在結(jié)果返回來(lái)時(shí)應(yīng)該發(fā)送一個(gè)Notification。
注意關(guān)心這個(gè)Notification的角色有可能并不是發(fā)起這個(gè)數(shù)據(jù)請(qǐng)求的那個(gè)角色。
舉例,調(diào)用遠(yuǎn)程服務(wù)的查詢并顯示返回的結(jié)果,這個(gè)過(guò)程通常會(huì)有以下幾步:
o
一個(gè)View Component觸發(fā)一個(gè)事件發(fā)起一個(gè)查詢請(qǐng)求。
o
它的Mediator響應(yīng):獲取相應(yīng)的RemoteProxy,設(shè)置它的searchCriteria 屬性。
o
Proxy的searchCriteria屬性其實(shí)是一個(gè)隱式setter,它會(huì)保存賦值,通過(guò)內(nèi)部的HTTPService(它會(huì)偵聽(tīng)result和fault事件)初始查詢請(qǐng)求。
o
當(dāng)服務(wù)返回結(jié)果時(shí),HTTPService會(huì)觸發(fā)ResultEvent事件,Proxy響應(yīng),把結(jié)果保存在公共屬性中。
o
Proxy然后發(fā)送一個(gè)Notification表示請(qǐng)求成功,這個(gè)Notification會(huì)綁定一個(gè)對(duì)數(shù)據(jù)對(duì)象的引用作為“報(bào)體”。
o
關(guān)心這個(gè)Notification的另外一個(gè)Mediator就會(huì)響應(yīng)這個(gè)Notification,把“報(bào)體”中的數(shù)據(jù)賦值給它所操作的View Component的dataProvider屬性。
再來(lái),假設(shè)有一個(gè)LoginProxy,它有一個(gè)LoginVO(一個(gè)Value Object;簡(jiǎn)單的數(shù)據(jù)載體類)。LoginVO可能看起來(lái)像這樣:
package com.me.myapp.model.vo
{
//把這個(gè)AS3 VO映射到Remote Class
[RemoteClass(alias="com.me.myapp.model.vo.LoginVO")]
[Bindable]
public class LoginVO
{
public var username: String;
public var password: String;
public var authToken: String;//登錄權(quán)限允許時(shí)服務(wù)器設(shè)置此值
}
}
LoginProxy對(duì)設(shè)錄錄外提供方法置登數(shù)據(jù),登(logging in),退出(logging out獲),取“錄登”時(shí)權(quán)標(biāo)識(shí)用到的限。
LoginProxy:
package com.me.myapp.model
{
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;
import mx.rpc.remoting.RemoteObject;
import org.puremvc.as3.interfaces.*;
import org.puremvc.as3.patterns.proxy.Proxy;
import com.me.myapp.model.vo.LoginVO;
// 用于用戶登錄的Proxy
public class LoginProxy extends Proxy implements IProxy {
public static const NAME:String = 'LoginProxy';
public static const LOGIN_SUCCESS:String = 'loginSuccess';
public static const LOGIN_FAILED:String = 'loginFailed';
public static const LOGGED_OUT:String = 'loggedOut';
private var loginService: RemoteObject;
public function LoginProxy () {
super( NAME, new LoginVO ( ) );
loginService = new RemoteObject();
loginService.source = "LoginService";
loginService.destination = "GenericDestination";
loginService.addEventListener( FaultEvent.FAULT, onFault );
loginService.login.addEventListener( ResultEvent.RESULT, onResult );
}
// 隱式getter,轉(zhuǎn)化data的類型
public function get loginVO( ) : LoginVO {
return data as LoginVO;
}
//如果logivVO中包含了authToken(授權(quán)標(biāo)識(shí))表示用戶登錄成功
public function get loggedIn():Boolean {
return ( authToken != null );
}
// 取得authToken
public function get authToken():String {
return loginVO.authToken;
}
//置用的限設(shè)戶權(quán)標(biāo)識(shí),登錄,退出,或登繼續(xù)嘗試錄。
public login( tryLogin:LoginVO ) : void {
if ( ! loggedIn ) {
loginVO.username= tryLogin.username;
loginVO.password = tryLogin.password;
} else {
logout();
login( tryLogin );
}
}
// 退出,地清空簡(jiǎn)單LoginVO
public function logout( ) : void
{
if ( loggedIn ) loginVO = new LoginVO( );
sendNotification( LOGGED_OUT );
}
//通知系登成功統(tǒng)錄
private function onResult( event:ResultEvent ) : void
{
setData( event.result ); // immediately available as loginVO
sendNotification( LOGIN_SUCCESS, authToken );
}
//通知系登失統(tǒng)錄敗
private function onFault( event:FaultEvent) : void
{
sendNotification( LOGIN_FAILED, event.fault.faultString );
}
}
}
一個(gè)LoginCommand會(huì)獲取LoginProxy,設(shè)置登錄的數(shù)據(jù),調(diào)用登錄函數(shù),呼叫登錄服務(wù)。
接下來(lái),可能一個(gè)GetPrefsCommand會(huì)響應(yīng)LOGIN_SUCCESS(登錄成功)這個(gè)Notification,從Notificaiton的“報(bào)體”中獲取authToken(授權(quán)標(biāo)識(shí)),接著呼叫下一個(gè)服務(wù),獲取用戶的(比如)配置信息(preferences)。
LoginCommand:
package com.me.myapp.controller {
import org.puremvc.as3.interfaces.*;
import org.puremvc.as3.patterns.command.*;
import com.me.myapp.model.LoginProxy;
import com.me.myapp.model.vo.LoginVO;
public class LoginCommand extends SimpleCommand {
override public function execute( note: INotification ) : void {
var loginVO : LoginVO = note.getBody() as LoginVO;
var loginProxy: LoginProxy;
loginProxy = facade.retrieveProxy( LoginProxy.NAME ) as LoginProxy;
loginProxy.login( loginVO );
}
}
}
GetPrefsCommand:
package com.me.myapp.controller {
import org.puremvc.as3.interfaces.*;
import org.puremvc.as3.patterns.command.*;
import com.me.myapp.model.LoginProxy;
import com.me.myapp.model.vo.LoginVO;
public class GetPrefsCommand extends SimpleCommand {
override public function execute( note: INotification ) : void {
var authToken : String = note.getBody() as String;
var prefsProxy : PrefsProxy;
prefsProxy = facade.retrieveProxy( PrefsProxy.NAME ) as PrefsProxy;
prefsProxy.getPrefs( authToken );
}
}
}
PureMVC框架的目標(biāo)很明確,即把程序分為低耦合的三層:Model、View和Controller。
降低模塊間的耦合性,各模塊如何結(jié)合在一起工作對(duì)于創(chuàng)建易擴(kuò)展,易維護(hù)的應(yīng)用程序是非常重要的。
在PureMVC實(shí)現(xiàn)的經(jīng)典MVC元設(shè)計(jì)模式中,這三部分由三個(gè)單例模式類管理,分別是Model、View和Controller。三者合稱為核心層或核心角色。
PureMVC中還有另外一個(gè)單例模式類——Façade,F(xiàn)açade提供了與核心層通信的唯一接口,以簡(jiǎn)化開(kāi)發(fā)復(fù)雜度。
Model 與 Proxy
Model保存對(duì)Proxy對(duì)象的引用,Proxy負(fù)責(zé)操作數(shù)據(jù)模型,與遠(yuǎn)程服務(wù)通信存取數(shù)據(jù)。
這樣保證了Model層的可移植性。
View 與 Mediator
View保存對(duì)Mediator對(duì)象的引用。由Mediator對(duì)象來(lái)操作具體的視圖組件(View Component,例如Flex的DataGrid組件),包括:添加事件監(jiān)聽(tīng)器,發(fā)送或接收Notification ,直接改變視圖組件的狀態(tài)。
這樣做實(shí)現(xiàn)了把視圖和控制它的邏輯分離開(kāi)來(lái)。
Controller 與 Command
Controller保存所有Command的映射。Command類是無(wú)狀態(tài)的,只在需要時(shí)才被創(chuàng)建。
PureMVC結(jié)構(gòu)
Controller 與 Command
Command可以獲取Proxy對(duì)象并與之交互,發(fā)送Notification,執(zhí)行其他的Command。經(jīng)常用于復(fù)雜的或系統(tǒng)范圍的操作,如應(yīng)用程序的“啟動(dòng)”和“關(guān)閉”。應(yīng)用程序的業(yè)務(wù)邏輯應(yīng)該在這里實(shí)現(xiàn)。
Façade 與 Core
Façade類應(yīng)用單例模式,它負(fù)責(zé)初始化核心層(Model,View和Controller),并能訪問(wèn)它們的Public方法。
這樣,在實(shí)際的應(yīng)用中,你只需繼承Façade類創(chuàng)建一個(gè)具體的Façade類就可以實(shí)現(xiàn)整個(gè)MVC模式,并不需要在代碼中導(dǎo)入編寫(xiě)Model,View和Controller類。
Proxy、Mediator和Command就可以通過(guò)創(chuàng)建的Façade類來(lái)相互訪問(wèn)通信。
Observer 與 Notification
PureMVC的通信并不采用Flash的EventDispatcher/Event,因?yàn)镻ureMVC可能運(yùn)行在沒(méi)有Flash Event和EventDispatcher類的環(huán)境中,它的通信是使用觀察者模式以一種松耦合的方式來(lái)實(shí)現(xiàn)的。
你可以不用關(guān)心PureMVC的Observer/Notification機(jī)制是怎么實(shí)現(xiàn)的,它已經(jīng)在框架內(nèi)部實(shí)現(xiàn)了。你只需要使用一個(gè)非常簡(jiǎn)單的方法從Proxy, Mediator, Command和Facade發(fā)送Notification,甚至不需要?jiǎng)?chuàng)建一個(gè)Notification實(shí)例。
Notification可以被用來(lái)觸發(fā)Command的執(zhí)行
Facade保存了Command與Notification之間的映射。當(dāng)Notification(通知)被
Notification可以被用來(lái)觸發(fā)Command的執(zhí)行
發(fā)出時(shí),對(duì)應(yīng)的Command(命令)就會(huì)自動(dòng)地由Controller執(zhí)行。Command實(shí)現(xiàn)復(fù)雜的交互,降低View和Model之間的耦合性。
Mediator發(fā)送、聲明、接收Notification
當(dāng)用View注冊(cè)Mediator時(shí),Mediator的listNotifications方法會(huì)被調(diào)用,以數(shù)組形式返回該Mediator對(duì)象所關(guān)心的所有Notification。
之后,當(dāng)系統(tǒng)其它角色發(fā)出同名的Notification(通知)時(shí),關(guān)心這個(gè)通知的Mediator都會(huì)調(diào)用handleNotification方法并將Notification以參數(shù)傳遞到方法。
Proxy發(fā)送,但不接收Notification
在很多場(chǎng)合下Proxy需要發(fā)送Notification(通知),比如:Proxy從遠(yuǎn)程服務(wù)接收到數(shù)據(jù)時(shí),發(fā)送Notification告訴系統(tǒng);或當(dāng)Proxy的數(shù)據(jù)被更新時(shí),發(fā)送Notification告訴系統(tǒng)。
如果讓Proxy也偵聽(tīng)Notification(通知)會(huì)導(dǎo)致它和View(視圖)層、Controller(控制)層的耦合度太高。
View和Controller必須監(jiān)聽(tīng)Proxy發(fā)送的Notification,因?yàn)樗鼈兊穆氊?zé)是通過(guò)可視化的界面使用戶能與Proxy持有的數(shù)據(jù)交互。
不過(guò)對(duì)View層和Controller層的改變不應(yīng)該影響到Model層。
例如,一個(gè)后臺(tái)管理程序和一個(gè)面向用戶程序可能共用一個(gè)Model類。如果只是用例不同,那么View/Controller通過(guò)傳遞不同的參數(shù)就可以共用相同的Model類。
MVC元設(shè)計(jì)模式的核心元素在PureMVC中體現(xiàn)為Model類、View類和Controller類。為了簡(jiǎn)化程序開(kāi)發(fā),PureMVC應(yīng)用了Façade模式。
Façade是Model、View和Controller三者的“經(jīng)紀(jì)人”。實(shí)際編寫(xiě)代碼時(shí)你并不用導(dǎo)入這三者的類文件,也不用直接使用它們。Façade類已經(jīng)在構(gòu)造方法包含了對(duì)核心MVC三者單例的構(gòu)造。
一般地,實(shí)際的應(yīng)用程序都有一個(gè)Façade子類,這個(gè)Façade類對(duì)象負(fù)責(zé)初始化Controller(控制器),建立Command與Notification名之間的映射,并執(zhí)行一個(gè)Command注冊(cè)所有的Model和View。
具體Façade是什么樣子的?
Façade類應(yīng)被當(dāng)成抽象類, 永遠(yuǎn)不被直接實(shí)例化。針對(duì)具體的應(yīng)用程序,你應(yīng)該具體編寫(xiě)Façade的子類,添加或重寫(xiě)Façade的方法來(lái)實(shí)現(xiàn)具體的應(yīng)用。按照慣例,這個(gè)類命名為“ApplicationFacade”(當(dāng)然,命名隨你喜歡),如前所述,它主要負(fù)責(zé)訪問(wèn)和通知Command,Mediator和Proxy。
通常,不同的運(yùn)行平臺(tái)都會(huì)創(chuàng)建視圖結(jié)構(gòu),盡管創(chuàng)建過(guò)程不一樣。(比如Flex中MXML程序負(fù)責(zé)實(shí)例化所有子視圖組件,F(xiàn)lash影片在Stage上構(gòu)建可視對(duì)象)。視圖結(jié)構(gòu)構(gòu)建完畢時(shí),整個(gè)PureMVC機(jī)制也已經(jīng)安置妥當(dāng)。
創(chuàng)建的Facade子類也被用來(lái)簡(jiǎn)化“啟動(dòng)”的過(guò)程。應(yīng)用程序調(diào)用Facade子類的startup方法,并傳遞自身的一個(gè)引用即完成啟動(dòng),使得應(yīng)用程序不需要過(guò)多了解PureMVC。
Façade類的內(nèi)容很簡(jiǎn)單。思考下面的例子:
ApplicationFacade.as:
package com.me.myapp
{
import org.puremvc.as3.interfaces.*;
import org.puremvc.as3..patterns.facade.*;
import com.me.myapp.view.*;
import com.me.myapp.model.*;
import com.me.myapp.controller.*;
// MyApp程序的Façade類
public class ApplicationFacade extends Façade implements IFacade
{
//定義Notification(通知)常量
public static const STARTUP:String = "startup";
public static const LOGIN:String = "login";
//得到ApplicationFacade單例的工廠方法
public static function getInstance() : ApplicationFacade
{
if ( instance == null ) instance = new ApplicationFacade( );
return instance as ApplicationFacade;
}
//注冊(cè)Command,建立Command與Notification之間的映射
override protected function initializeController( ) : void
{
super.initializeController();
registerCommand( STARTUP, StartupCommand );
registerCommand( LOGIN, LoginCommand );
registerCommand( LoginProxy.LOGIN_SUCCESS, GetPrefsCommand );
}
//啟動(dòng)PureMVC,在應(yīng)用程序中調(diào)用此方法,并傳遞應(yīng)用程序本身的引用 public function startup( app:MyApp ) : void
{
sendNotification( STARTUP, app );
}
}
}
上述代碼需要注意以下幾點(diǎn):
o
ApplicationFacade繼承自PureMVC的Façade類,F(xiàn)açade類實(shí)現(xiàn)了IFacade接口。
o
這個(gè)例子里ApplicationFacade沒(méi)有重寫(xiě)構(gòu)造方法。如果重寫(xiě)構(gòu)造方法,應(yīng)該在構(gòu)造方法里先調(diào)用父類的構(gòu)造方法。
o
類方法getInstance用于返回ApplicationFacade的單例,并將實(shí)例保存在父類的一個(gè)protect變量中。在返回該實(shí)例之前必須先把它轉(zhuǎn)化為ApplicationFacade類型。
o
o
初始化Controller(控制器),并建立Command與Notification之間的映射,當(dāng)Notification(通知)發(fā)出時(shí)相關(guān)的Command(命令)就會(huì)被執(zhí)行。
o
提供一個(gè)帶有應(yīng)用程序類型參數(shù)的startup方法,該參數(shù)能過(guò)Notification傳遞到StartupCommand。
實(shí)現(xiàn)這些只需要繼承父類很少的功能。
初始化Façade
PureMVC的Façade類在構(gòu)造方法中初始化了Model、View和Controller對(duì)象,并把對(duì)它們的引用保存在成員變量。
這樣,F(xiàn)açade就可以訪問(wèn)Model、View和Controller了。這樣把對(duì)核心層的操作都集中在Façade,避免開(kāi)發(fā)者直接操作核心層。
那么,在具體的應(yīng)用程序中,F(xiàn)açade是何時(shí)何地初始化的呢?請(qǐng)查看下面的Flex代碼:
MyApp.mxml:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
creationComplete="façade.startup(this)”>
<mx:Script>
<![CDATA[
//獲取ApplicationFacade
import com.me.myapp.ApplicationFacade;
private var facade:ApplicationFacade = ApplicationFacade.getInstance();
]]>
</mx:Script>
<!—視圖層碼的其他代-->
</mx:Application>
上面這個(gè)程序很簡(jiǎn)單:
程序運(yùn)行時(shí)創(chuàng)建視圖結(jié)構(gòu),得到ApplicationFaçade實(shí)例,調(diào)用它的startup方法。
注意:在AIR中,我們會(huì)使用“applicationComplete”代替這里的“creationComplete”;在Flash中,我們可以把對(duì)startup的調(diào)用放在第1幀或文檔類里。
請(qǐng)注意例子中以下幾點(diǎn):
o
我們使用了MXML標(biāo)簽,這種普遍的方式,創(chuàng)建程序的界面。以<mx:Application>標(biāo)簽開(kāi)始,包含組件或容器,不管是Flex內(nèi)嵌的組件還是自定義的。
o
聲明并初始化了一個(gè)私有變量,(用于)獲取ApplicationFacade單例。
o
我們初始化這個(gè)變量時(shí)使用了getInstance類方法,在應(yīng)用程序的creationComplete事件觸發(fā)時(shí),F(xiàn)acade和相關(guān)的Model,View,Controller都已經(jīng)實(shí)例化完畢(盡管此時(shí)還沒(méi)有創(chuàng)建任何Mediator和Proxy)。
o
我們?cè)贏pplication標(biāo)簽中指定了對(duì)creationComplete事件的處理過(guò)程:調(diào)用startup方法,并傳參主程序的引用。
注意:除了頂層的Application,其他視圖組件都不用(不應(yīng)該)和Façade交互。
頂層的Application(或Flash里的Movie)構(gòu)建視圖結(jié)構(gòu)、初始化Façade,然后“啟動(dòng)”整個(gè)PureMVC機(jī)制。
Notification
PureMVC使用了觀察者模式,所以各層之間能以一種松耦合的方式通信,并且與平臺(tái)無(wú)關(guān)。
ActionScript語(yǔ)言本身沒(méi)有提供flash.events包中的事件模型。況且PureMVC框架并不是只針對(duì)AS語(yǔ)言,它被移植到其他的一些平臺(tái)像C#、J2ME,所以它不會(huì)使用這些只有在Flash平臺(tái)上才有的類,它采用自己的通信機(jī)制(即Notification)。
Notification(通知)機(jī)制并不僅僅是Event(事件)機(jī)制的替代品,它們的工作方式有本質(zhì)上的不同。但這兩者相互協(xié)作可以提高視圖組件的可重用性,甚至,如果設(shè)計(jì)得當(dāng),視圖組件可以和PureMVC“脫耦”。
Event 與 Notification
Event是由實(shí)現(xiàn)IeventDispatcher接口的Flash顯示對(duì)象廣播的,Event會(huì)在整個(gè)顯示對(duì)象層中“冒泡”,這樣可以讓父級(jí)(或父級(jí)的父級(jí),等)對(duì)象處理事件。
Event機(jī)制是一個(gè)“責(zé)任鏈”的形式:除了那些可以直接引用事件發(fā)起者(dispatcher)并偵聽(tīng)它事件的對(duì)象,只有和dispatcher是父子關(guān)系的對(duì)象才會(huì)接收到事件,并對(duì)事件做出響應(yīng)動(dòng)作。
Event 與 Notification
Facade 和Proxy只能發(fā)送Notification,Mediators既可以發(fā)送也可以接收Notification,Notification被映射到 Command,同時(shí)Command也可以發(fā)送Notification。這是一種“發(fā)布/訂閱”機(jī)制,所有的觀察者都可以收到相同的通知。例如多個(gè)書(shū)刊訂閱者可以訂閱同一份雜志,當(dāng)雜志有新刊出版時(shí),所有的訂閱者都會(huì)被通知。
Notification(通知)有一個(gè)可選的“報(bào)體”,“報(bào)體”可以是任意ActionScript對(duì)象。[譯注:“報(bào)體”被用來(lái)在Notification中攜帶參數(shù),比如在上一個(gè)例子中我們發(fā)送了一個(gè)攜帶“app”參數(shù),名字叫STARTUP的Notification。]
與Flash Event不同,不需要自定義一個(gè)Notification類來(lái)傳值(注:Notification使用Object類型的"報(bào)體"來(lái)傳值)。當(dāng)然,你可以自定義Notification類以強(qiáng)類型的方式交互。這樣做的好處是編譯時(shí)有類型檢查,但卻會(huì)導(dǎo)致你必須管理很多Notification類。
Notification另有一個(gè)可選的“類型”參數(shù),用于讓接收者作為鑒別依據(jù)。
舉例,有一個(gè)文檔編輯器程序,當(dāng)一個(gè)文檔對(duì)象被打開(kāi)時(shí),程序會(huì)創(chuàng)建對(duì)應(yīng)的Proxy對(duì)象和Mediator對(duì)象(Mediator對(duì)象由視圖組件使用)。Proxy對(duì)象就可能需要在發(fā)送的Notification中加入“類型”參數(shù),以讓Mediator能夠唯一標(biāo)識(shí)文檔對(duì)象。
所有注冊(cè)了這個(gè)Proxy對(duì)象Noification的Mediator都會(huì)接收到這個(gè)Notification(通知),但它們可以根據(jù)Notification中“類型”參數(shù)決定是否做出反應(yīng)動(dòng)作。
定義Notification和Event常量
如前所述,公共的Notification名稱常量很適合定義在Façade中。所有與Notification交互的參與者都是Facade的協(xié)作者(collaborator)。
定義Notification和Event常量
當(dāng)這些Notification的名稱常量需要被其他的程序訪問(wèn)時(shí),我們可以使用單獨(dú)的“ApplicationConstants”類來(lái)存放這些Notification名稱常量定義。
不管什么時(shí)候,都應(yīng)該把Notification(通知)名稱定義為常量,需要引用一個(gè)Notification時(shí)就使用它的名稱常量,這樣做可以避免一些編譯時(shí)無(wú)法發(fā)現(xiàn)的錯(cuò)誤。因?yàn)榫幾g器可以檢查常量;而使用字符串,如果你手誤輸入錯(cuò)誤的字符串,編譯器也不法知道,也無(wú)從報(bào)錯(cuò)。
永遠(yuǎn)不要把Event的名稱定義在 Façade類里。應(yīng)該把Event名稱常量定義在那些發(fā)送事件的地方,或者就定義在Event類里。
在物理層面上審視Application,如果,View Component和Data Object在和相應(yīng)的Mediator和Proxy通信時(shí)是通過(guò)觸發(fā)Event(事件)而不是通過(guò)調(diào)用方法或發(fā)送Notification,那么View Component和Data Object就可以保持重用性。
如果一個(gè)View Component(或Data Object)觸發(fā)了一個(gè)對(duì)應(yīng)Mediator(或Proxy)正在偵聽(tīng)的Event,那么只有這一對(duì)協(xié)作者(view component-mediator,data object-proxy)需要知道事件的名稱,接下來(lái)Mediator(或Proxy)與PureMVC系統(tǒng)其他部分的通信是通過(guò)Notification進(jìn)行。
雖然協(xié)作者間(Mediator/View,或Proxy/Data)的關(guān)系是有必要緊耦合了。但“協(xié)作者對(duì)”與程序結(jié)構(gòu)的其它部分形成了松耦合,這樣在需求變更時(shí)代碼的修改范圍能有效的縮小。
Command
ApplicationFacade需要在啟動(dòng)時(shí)初始化Controller,建立Notification與Command的映射。
Controller會(huì)注冊(cè)偵聽(tīng)每一個(gè)Notification,當(dāng)被通知到時(shí),Controller會(huì)實(shí)例化一個(gè)該Notification對(duì)應(yīng)的Command類的對(duì)象。最后,將Notification作為參數(shù)傳遞給execute方法。
Command對(duì)象是無(wú)狀態(tài)的;只有在需要的時(shí)候(Controller收到相應(yīng)的Notification)才會(huì)被創(chuàng)建,并且在被執(zhí)行(調(diào)用execute方法)之后就會(huì)被刪除。所以不要在那些生命周期長(zhǎng)的對(duì)象(long-living object)里引用Command對(duì)象。
SimpleCommand和MacroCommand的使用
Command要實(shí)現(xiàn)ICommand接口。在PureMVC中有兩個(gè)類實(shí)現(xiàn)了ICommand接口:SimpleCommand、MacroCommand。
SimpleCommand只有一個(gè)execute方法,execute方法接受一個(gè)Inotification實(shí)例做為參數(shù)。實(shí)際應(yīng)用中,你只需要重寫(xiě)這個(gè)方法就行了。
MacroCommand讓你可以順序執(zhí)行多個(gè)Command。每個(gè)執(zhí)行都會(huì)創(chuàng)建一個(gè)Command對(duì)象并傳參一個(gè)對(duì)源Notification的引用。
MacroCommand在構(gòu)造方法調(diào)用自身的initializeMacroCommand方法。實(shí)際應(yīng)用中,你需重寫(xiě)這個(gè)方法,調(diào)用addSubCommand添加子Command。你可以任意組合SimpleCommand和MacroCommand成為一個(gè)新的Command。
降低Command與Mediator, Proxy的耦合度
通過(guò)發(fā)送Notification通知Controller來(lái)執(zhí)行Command,而且只能由Controller實(shí)例化并執(zhí)行Command。
為了和系統(tǒng)其他部分交互與通信,Command可能需要:
o
注冊(cè)、刪除Mediator、Proxy和Command,或者檢查它們是否已經(jīng)注冊(cè)。
降低Command與Mediator, Proxy的耦合度
o
發(fā)送Notification通知Command或Mediator做出響應(yīng)。
o
獲取Proxy和Mediator對(duì)象并直接操作它們。
Command使我們可以很容易地切換視圖元素狀態(tài),或傳送數(shù)據(jù)給它。
Command可以調(diào)用多個(gè)Proxy執(zhí)行事務(wù)處理,當(dāng)事務(wù)結(jié)束后,發(fā)送Notification或處理異常和失敗。
復(fù)雜的操作與業(yè)務(wù)邏輯
在程序的很多地方你都可以放置代碼(Command,Mediator和Proxy);不可避免地會(huì)不斷碰到一個(gè)問(wèn)題:
哪些代碼應(yīng)該放在哪里?確切的說(shuō),Command應(yīng)該做什么?
程序中的邏輯分為Business Logic(業(yè)務(wù)邏輯)和Domain Logic(域邏輯),首先需要知道這兩者之間的差別。
Command管理應(yīng)用程序的Business Logic(業(yè)務(wù)邏輯),與Domain Logic(域邏輯)相區(qū)別,Business Logic(業(yè)務(wù)邏輯)要協(xié)調(diào)Model與視圖狀態(tài)。
Model通過(guò)使用Proxy來(lái)保證數(shù)據(jù)的完整性、一致性。Proxy集中程序的Domain Logic(域邏輯),并對(duì)外公布操作數(shù)據(jù)對(duì)象的API。它封裝了所有對(duì)數(shù)據(jù)模型的操作,不管數(shù)據(jù)是客戶端還是服務(wù)器端的,對(duì)程序其他部分來(lái)說(shuō)就是數(shù)據(jù)的訪問(wèn)是同步還是異步的。
Command可能被用于實(shí)現(xiàn)一些復(fù)雜、必須按照一定順序的系統(tǒng)行為,上一步動(dòng)作的結(jié)果可能會(huì)流入一下個(gè)動(dòng)作。.
Mediator和Proxy可以提供一些操作接口讓Command調(diào)用來(lái)管理View Component和Data Object,同時(shí)對(duì)Command隱藏具體操作的細(xì)節(jié)。
復(fù)雜的操作與業(yè)務(wù)邏輯
我們這里談?wù)摰腣iew Component時(shí)就是指像按鈕這種用戶直接交互的小東西。而Data Object則是指能以任意結(jié)構(gòu)存儲(chǔ)數(shù)據(jù)的對(duì)象。
Command與Mediator和Proxy交互,應(yīng)避免Mediator與Proxy直接交互。請(qǐng)看下面這個(gè)用于程序“啟動(dòng)”的Command:
StartupCommand.as:
package com.me.myapp.controller
{
import org.puremvc.as3.interfaces.*;
import org.puremvc.as3.patterns.command.*;
import com.me.myapp.controller.*;
// 開(kāi)時(shí)執(zhí)程序始行的MacroCommand.
public class StartupCommand extends MacroCommand
{
//添加子Command初始化MacroCommand.
override protected function initializeMacroCommand() : void
{
addSubCommand( ModelPrepCommand );
addSubCommand( ViewPrepCommand );
}
}
}
這是一個(gè)添加了兩個(gè)子Command的MacroCommand,執(zhí)行時(shí)兩個(gè)版子命令會(huì)按照“先進(jìn)先出”(FIFO)的順序被執(zhí)行。
復(fù)雜的操作與業(yè)務(wù)邏輯
這個(gè)復(fù)合命令定義了PureMVC在“開(kāi)啟”(startup)時(shí)的動(dòng)作序列。但具體的,我們應(yīng)該做什么?按照什么順序?
在用戶與數(shù)據(jù)交互之前,Model必須處于一種一致的已知的狀態(tài)。一旦Model初始化完成,視圖就可以顯示數(shù)據(jù)允許用戶操作與之交互。
因此,一般“開(kāi)啟”(startup)過(guò)程有兩個(gè)主要的動(dòng)作:Model初始化與View初始化。
ModelPrepCommand.as:
package com.me.myapp.controller
{
import org.puremvc.as3.interfaces.*;
import org.puremvc.as3.patterns.observer.*;
import org.puremvc.as3.patterns.command.*;
import com.me.myapp.*;
import com.me.myapp.model.*;
//創(chuàng)建Proxy對(duì)象,并注冊(cè)。
public class ModelPrepCommand extends SimpleCommand
{
//由MacroCommand調(diào)用
override public function execute( note : INotification ) : void
{
facade.registerProxy( new SearchProxy() );
facade.registerProxy( new PrefsProxy() );
facade.registerProxy( new UsersProxy() );
}
}
}
Model的初始化通常比較簡(jiǎn)單:創(chuàng)建并注冊(cè)在“開(kāi)啟”過(guò)程中需要用到的Proxy。
上面這個(gè)ModelPrepCommand類是一個(gè)SimpleCommand例子,它功能就是初始化Model,它是前面那個(gè)MacroCommand的第一個(gè)子命令,所以它會(huì)最先被執(zhí)行。
通常具體的Façade對(duì)象,它創(chuàng)建并注冊(cè)了多個(gè)在“啟動(dòng)”(startup)過(guò)程中會(huì)用到的Proxy類。注意這里Command并沒(méi)有操作或初始任何的Model數(shù)據(jù)。Proxy的職責(zé)才是取得,創(chuàng)建,和初始化數(shù)據(jù)對(duì)象。
ViewPrepCommand.as:
package com.me.myapp.controller
{
import org.puremvc.as3.interfaces.*;
import org.puremvc.as3.patterns.observer.*;
import org.puremvc.as3.patterns.command.*;
import com.me.myapp.*;
import com.me.myapp.view.*;
//創(chuàng)建Mediator們,并把它注冊(cè)到View.
public class ViewPrepCommand extends SimpleCommand
{
override public function execute( note : INotification ) : void {
var app:MyApp = note.getBody() as MyApp;
facade.registerMediator( new ApplicationMediator( app ) );
}
}
}
這個(gè)SimpleCommand的功能是初始化View。它是之前那個(gè)MacroCommand的最后一個(gè)子命令,所以它會(huì)被最后一個(gè)執(zhí)行。
注意這個(gè)命令唯一創(chuàng)建并注冊(cè)的Mediator是ApplicationMediator,ApplicationMediator被用于操作Appliction View。
更深入一點(diǎn):它向創(chuàng)建Mediator對(duì)象時(shí),向Mediator構(gòu)造函數(shù)傳參Notification的“報(bào)體”。這個(gè)“報(bào)體”是個(gè)對(duì)Application的引用,它是在最初的“啟動(dòng)通知”(STARTUP Notification)發(fā)出時(shí)由Application自身發(fā)送的。(請(qǐng)查看之前的MyApp范例。)
Application是個(gè)有點(diǎn)特殊的視圖組件(View Component),它包含其他所有視圖組件(View Component)并在啟動(dòng)時(shí)創(chuàng)建它們。
為了和系統(tǒng)其他部分通信,視圖組件需要使用Mediator。創(chuàng)建Mediator對(duì)象需要引用此Mediaotr管理的視圖組件(View component)的引用,這些Mediator和View Component的對(duì)應(yīng)關(guān)系只有Application知道。
比較特殊的是Application的Mediator,它是唯一的被允許知道Application一切的類,所以我們會(huì)在Application Mediator的構(gòu)造函數(shù)中創(chuàng)建其他的Mediator對(duì)象。
上面的三個(gè)Command初始化了Model和View。這樣做Command不需要知道太多Model和View的細(xì)節(jié)。
當(dāng)Model或View的實(shí)現(xiàn)發(fā)生改變時(shí)就只需要改變Model和View和相應(yīng)部分即可,而不需要改變Command的邏輯。
Command的業(yè)務(wù)邏輯應(yīng)該避免被Model或View的變化而受到影響。
復(fù)雜的操作與業(yè)務(wù)邏輯
Model應(yīng)該邏輯封裝域(domain logic)證,保數(shù)據(jù)的完整性。Command則處理事務(wù)業(yè)務(wù)邏輯協(xié)調(diào)或,多個(gè)Proxy處的操作,理異常等。
Mediator
Mediator是視圖組件(View Component,例如Flex的DataGrid或Flash的
MovieClip)與系統(tǒng)其他部分交互的中介器。
在基于Flash的應(yīng)用程序中,Mediator 偵聽(tīng)View Component來(lái)處理用戶動(dòng)作和Component的數(shù)據(jù)請(qǐng)求。Mediator通過(guò)發(fā)送和接收Notification來(lái)與程序其他部分通信。
Mediator的職責(zé)
Flash、Flex和AIR框架都提供了豐富強(qiáng)大的交互UI組件。你可以擴(kuò)展這些組件或者編寫(xiě)自己的組件為用戶提供更強(qiáng)大的交互方式和數(shù)據(jù)呈現(xiàn)方式。
在不遠(yuǎn)的將來(lái),會(huì)有越來(lái)越多的平臺(tái)運(yùn)行ActionScript語(yǔ)言。PureMVC也已經(jīng)有移植到其他平臺(tái)上,包括Silverlight和J2ME,拓寬了PureMVC技術(shù)的RIA開(kāi)發(fā)道路。
PureMVC的目標(biāo)之一就是保持平臺(tái)無(wú)關(guān)性,不管你使用什么技術(shù)什么UI組件什么數(shù)據(jù)結(jié)構(gòu)都能應(yīng)用PureMVC框架。
對(duì)基于PureMVC的應(yīng)用程序來(lái)說(shuō),View Component可以是任意的UI Component,不用管所處的框架是什么(Flex,Java還是C#),也不用管它有多少個(gè)組件。一個(gè)View Component應(yīng)該把盡可能自己的狀態(tài)和操作封裝起來(lái),對(duì)外只提供事件、方法和屬性的簡(jiǎn)單的API。
Mediator保存了一個(gè)或多個(gè)View Component的引用,通過(guò)View Component自身提供的API管理它們。
Mediator的主要職責(zé)是處理View Component派發(fā)的事件和系統(tǒng)其他部分發(fā)出來(lái)的Notification(通知)。
因?yàn)镸ediator也會(huì)經(jīng)常和Proxy交互,所以經(jīng)常在Mediator的構(gòu)造方法中取得Proxy實(shí)例的引用并保存在Mediator的屬性中,這樣避免頻繁的獲取Proxy實(shí)例。
轉(zhuǎn)化View Component類型
PureMVC的Mediator基類在構(gòu)造方法中提供兩個(gè)參數(shù):name(名稱)和一個(gè)Object類型的對(duì)象。
這個(gè)Mediator子類會(huì)在構(gòu)造函數(shù)中把它的View Component傳參給父類,它會(huì)在內(nèi)部賦值給一個(gè)protect屬性:viewComponent,并傳化為Object類型。
在Mediator被構(gòu)造之后,你可能通過(guò)調(diào)用它的setViewComponent函數(shù)來(lái)動(dòng)態(tài)給它的View Component賦值(修改)。
之后,每一次需要訪問(wèn)這個(gè)Object的API時(shí),你都要手動(dòng)把這個(gè)Object轉(zhuǎn)化成它的真正類型。這是一項(xiàng)煩瑣重復(fù)的工作。
ActionScript語(yǔ)言提供隱式setter和getter。隱式的setter和getter看起來(lái)像方法,但對(duì)外是屬性。實(shí)踐證明setter和getter對(duì)解決頻繁轉(zhuǎn)換類型問(wèn)題是很好的解決方法。
一種常用做法是在具體的Mediator中提供一個(gè)隱式getter來(lái)對(duì)View Component進(jìn)行類型轉(zhuǎn)換,注意給這個(gè)getter命名一個(gè)合適的名字。
示例:
protected function get controlBar() : MyAppControlBar
{
return viewComponent as MyAppControlBar;
}
之后,在Mediator的其他地方,我們不再這樣做了:
MyAppControlBar ( viewComponent ).searchSelction = MyAppControlBar.NONE_SELECTED;
我們會(huì)這樣:
controlBar.searchSelction = MyAppControlBar.NONE_SELECTED;
監(jiān)應(yīng)聽(tīng)并響View Component
通常一個(gè)Mediator只對(duì)應(yīng)一個(gè)View Component,但卻可能需要管理多個(gè)UI控件,比如一個(gè)ApplicationToolBar和它包含的button或control。我們可以把一組相關(guān)的Control(比如from)放在一個(gè)View Component里,把這組相關(guān)的控件當(dāng)作一個(gè)View Component,對(duì)Mediator來(lái)說(shuō),這些控件是這個(gè)View Component的屬性,應(yīng)盡可能的封裝它們的操作與之交互。
Mediator負(fù)責(zé)處理與Controller層、Model層交互,在收到相關(guān)Notification時(shí)更新View Component。
在Flash平臺(tái)上,一般在構(gòu)造方法中或setViewComponent方法被調(diào)用后,給View Component添加事件監(jiān)聽(tīng)器。
controlBar.addEventListener( AppControlBar.BEGIN_SEARCH, onBeginSearch );
監(jiān)應(yīng)聽(tīng)并響View Component
Mediator按需求對(duì)事件做出響應(yīng)。
一般地,一個(gè)Mediator的事件響應(yīng)會(huì)有以下幾種處理:
o
檢查事件類型或事件的自定義內(nèi)容。
o
檢查或修改View Component的屬性(或調(diào)用提供的方法)。
o
檢查或修改Proxy對(duì)象公布的屬性(或調(diào)用提供的方法)。
o
發(fā)送一個(gè)或多個(gè)Notification,通知?jiǎng)e的Mediatora或Command作出響應(yīng)(甚至有可能發(fā)送給自身)。
下面是一些有用的經(jīng)驗(yàn):
o
如果有多個(gè)的Mediator對(duì)同一個(gè)事件做出響應(yīng),那么應(yīng)該發(fā)送一個(gè)Notification,然后相關(guān)的Mediator做出各自的響應(yīng)。
o
如果一個(gè)Mediator需要和其他的Mediator進(jìn)行大量的交互,那么一個(gè)好方法是利用Command把交互步驟定義在一個(gè)地方。
o
不應(yīng)該讓一個(gè)Mediator直接去獲取調(diào)用其他的Mediator,在Mediator中定義這樣的操作本身就是錯(cuò)誤的。
o
Proxy是有狀態(tài)的,當(dāng)狀態(tài)發(fā)生變化時(shí)發(fā)送Notification通知Mediator,將數(shù)據(jù)的變化反映到視圖。
與給視圖添加嚴(yán)格的事件監(jiān)聽(tīng)器相比,Mediator與PureMVC系統(tǒng)的其它部分關(guān)聯(lián)起來(lái)是簡(jiǎn)單而且自動(dòng)化的。
在 Mediator實(shí)例化時(shí),PureMVC會(huì)調(diào)用Mediator的listNotificationInterests方法查詢其關(guān)心的 Notification,Mediator則在listNotificationInterests方法中以數(shù)據(jù)形式返回這些Notification 名稱。
最簡(jiǎn)單的方法是返回一個(gè)由Notification名稱組成的匿名數(shù)組。前面提過(guò),Notification名稱通常以常量形式定義在Façade類中。
下面是個(gè)例子:
override public function listNotificationInterests() : Array
{
return [
ApplicationFacade.SEARCH_FAILED,
ApplicationFacade.SEARCH_SUCCESS
];
}
當(dāng)這個(gè)數(shù)組里的一個(gè)Notification被系統(tǒng)的其他部分(也可能是Mediator對(duì)象自身)發(fā)出時(shí),Mediator對(duì)象的handleNotification函數(shù)會(huì)被調(diào)用,并傳進(jìn)Notification參數(shù)。
在handleNotification函數(shù)里,使用的是“switch/case”而不是“if/else if”的分支控制,因?yàn)榍罢吒鬃x,而且增加、刪除一個(gè)Notification比較方便。
本質(zhì)上來(lái)講,在收到一個(gè)Notification時(shí)Mediator是所要操作的是很少的。有時(shí)候(偶爾),我們需要從Notification里獲取有關(guān)Proxy的信息,但記住,不應(yīng)該讓處理Notification的方法負(fù)責(zé)復(fù)雜邏輯。業(yè)務(wù)邏輯應(yīng)該放在Command中而非在Mediator中。
在Mediator里處理Notification
override public function handleNotification( note : INotification ) : void
{
switch ( note.getName() )
{
case ApplicationFacade.SEARCH_FAILED:
controlBar.status = AppControlBar.STATUS_FAILED;
controlBar.searchText.setFocus();
break;
case ApplicationFacade.SEARCH_SUCCESS:
controlBar.status = AppControlBar.STATUS_SUCCESS;
break;
}
}
}
還有,一般一個(gè)Mediator(handleNotification方法)處理的Notification應(yīng)該在4、5個(gè)之內(nèi)。
還要注意的是,Mediator的職責(zé)應(yīng)該要細(xì)分。如果處理的Notification很多,則意味著Mediator需要被拆分,在拆分后的子模塊的Mediator里處理要比全部放在一起更好。
通常對(duì)于每一個(gè)事件都需要添加一個(gè)監(jiān)聽(tīng)方法,一般這些方法只是發(fā)送Notification,處理邏輯應(yīng)該不復(fù)雜,也不應(yīng)該涉及太多的View Component細(xì)節(jié),因?yàn)閷?duì)視圖View Component的處理應(yīng)該盡可能的封裝起來(lái)。
對(duì)于Notification,Mediator有唯一的一個(gè)處理方法,這個(gè)方法中它會(huì)(通過(guò)switch/case)處理所有的Notification。
最好是把對(duì)所有Notification的處理放在handleNotification方法中,使用switch/case來(lái)區(qū)分Notification名稱。
已經(jīng)有很多關(guān)于“switch/case”用法的爭(zhēng)論,很多開(kāi)發(fā)者認(rèn)為它有局限性,因?yàn)樗械那闆r都在一個(gè)函數(shù)里處理了。不過(guò),單一的Notification處理函數(shù)以及“switch/case”分支風(fēng)格是PureMVC特意選定的。這樣做可以限定Mediator的代碼量,并讓代碼保持PureMVC推薦的結(jié)構(gòu)。
Mediator是被用來(lái)負(fù)責(zé)View Component和系統(tǒng)其他部分的通信的。
就像一個(gè)翻譯員在聯(lián)合國(guó)大會(huì)(UN conference)上給大使們翻譯對(duì)話。她應(yīng)當(dāng)盡量少做翻譯(轉(zhuǎn)發(fā)信息)之外的事情,偶爾可以引用一下比喻、事實(shí)。Mediator在PureMVC中也是這樣的一個(gè)角色。
Mediator和Proxy之間、Mediator和其他Mediator之間的耦合
View本質(zhì)上是顯示Model的數(shù)據(jù)并讓用戶能與之交互,我們期望一種單向依賴,即View依賴于Model,而Model卻不依賴于View。View必須知道Model的數(shù)據(jù)是什么,但Model卻并不需要知道View的任何內(nèi)容。
雖然Mediator可以任意訪問(wèn)Proxy,通過(guò)Proxy的API讀取、操作Data Object,但是,由Command來(lái)做這些工作可以實(shí)現(xiàn)View和Model之間的松耦合。
同樣的情況,雖然Mediator可以從View獲取其他的Mediator,通過(guò)API訪問(wèn)、操作它們。但這樣是很不好的,它會(huì)導(dǎo)致View下成員的相互依賴,這違反了“改變一個(gè)不影響其他”的目的。
如果一個(gè)Mediator要和其他Mediator通信,那它應(yīng)該發(fā)送Notification來(lái)實(shí)現(xiàn),而不是直接引用這個(gè)Mediator來(lái)操作。
Mediator對(duì)外不應(yīng)該公布操作View Component的函數(shù)。而是自己接收Notification做出響應(yīng)來(lái)實(shí)現(xiàn)。
如果在Mediator里有很多對(duì)View Component的操作(響應(yīng)Event或Notification),那么應(yīng)該考慮將這些操作封裝為View Component的一個(gè)方法,提高可重用性。
如果一個(gè)Mediator有太多的對(duì)Proxy及其數(shù)據(jù)的操作,那么,應(yīng)該把這些代碼重構(gòu)在Command內(nèi),簡(jiǎn)化Mediator,把業(yè)務(wù)邏輯(Business Logic)移放到Command上,這樣Command可以被View的其他部分重用,還會(huì)實(shí)現(xiàn)View和Model之間的松耦合提高擴(kuò)展性。
用戶與View Component和Mediator的交互
假如有一個(gè)含表單的LoginPanel組件。對(duì)應(yīng)有一個(gè)LoginPanelMediator,負(fù)責(zé)與LoginPanel交互并響應(yīng)它的輸入信息發(fā)送登錄請(qǐng)求。
LoginPanel和LoginPanelMediator之間的協(xié)作表現(xiàn)為:LoginPanel在用戶輸入完信息要登錄時(shí)發(fā)送一個(gè)TRY_LOGIN的事件,LoginPanelMediator處理這個(gè)事件,處理方法是發(fā)送一個(gè)以組件包含的LoginVO為“報(bào)體”的Notification(通知)。
LoginPanel.mxml:
<?xml version="1.0" encoding="utf-8"?>
<mx:Panel xmlns:mx="http://www.adobe.com/2006/mxml"
title="Login" status=”{loginStatus}”>
<!—
The events this component dispatches. Unfortunately we can’t use
the constant name here, because MetaData is a compiler directive
-->
<mx:MetaData>
[Event('tryLogin')];
</mx:MetaData>
<mx:Script>
<![CDATA[
import com.me.myapp.model.vo.LoginVO;
// 表單項(xiàng)與LoginVO對(duì)象的屬性雙向綁定。
[Bindable] public var loginVO:LoginVO = new LoginVO();
[Bindable] public var loginStatus:String = NOT_LOGGED_IN;
//義定Event名稱常量
public static const TRY_LOGIN:String='tryLogin';
public static const LOGGED_IN:String='Logged In';
public static const NOT_LOGGED_IN:String='Enter Credentials';
]]>
</mx:Script>
<mx:Binding source="username.text" destination="loginVO.username"/>
<mx:Binding source="password.text" destination="loginVO.password"/>
用戶與View Component和Mediator的交互
<!—The Login Form -->
<mx:Form id="loginForm" >
<mx:FormItem label="Username:">
<mx:TextInput id="username" text="{loginVO.username}" />
</mx:FormItem>
<mx:FormItem label="Password:">
<mx:TextInput id="password" text="{loginVO.password}"
displayAsPassword="true" />
</mx:FormItem>
<mx:FormItem >
<mx:Button label="Login" enabled="{loginStatus == NOT_LOGGED_IN}”
click="dispatchEvent( new Event(TRY_LOGIN, true ));"/>
</mx:FormItem>
</mx:Form>
</mx:Panel>
LoginPanel組件包含有一個(gè)用用戶表單輸入新創(chuàng)建的LoginVO對(duì)象,當(dāng)用戶單擊“Login”按鈕時(shí)觸發(fā)一個(gè)事件,接下來(lái)的事情由LoginPanelMediator接管。
這樣View Component的角色就是簡(jiǎn)單收集數(shù)據(jù),收集完數(shù)據(jù)通知系統(tǒng)。
可以完善的地方是只有當(dāng)username和password都有內(nèi)容時(shí)才讓login按鈕可用(enable),這樣可以避免惡意登錄。
View Component對(duì)外隱藏自己的內(nèi)部實(shí)現(xiàn),它由Mediator使用的整個(gè)API包括:一個(gè)TRY_LOGIN事件,一個(gè)LoginVO屬性和Panel的狀態(tài)屬性。
LoginPanelMediator會(huì)對(duì)LOGIN_FAILED和LOGIN_SUCCESS通知做出反應(yīng),設(shè)置LoginPanel的狀態(tài)。
LoginPanelMediator.as:
package com.me.myapp.view
{
import flash.events.Event;
import org.puremvc.as3.interfaces.*;
import org.puremvc.as3.patterns.mediator.Mediator;
import com.me.myapp.model.LoginProxy;
import com.me.myapp.model.vo.LoginVO;
import com.me.myapp.ApplicationFacade;
import com.me.myapp.view.components.LoginPanel;
// LoginPanel視圖的Mediator
public class LoginPanelMediator extends Mediator implements IMediator
{
public static const NAME:String = 'LoginPanelMediator';
public function LoginPanelMediator( viewComponent:LoginPanel )
{
super( NAME, viewComponent );
LoginPanel.addEventListener( LoginPanel.TRY_LOGIN, onTryLogin );
}
// 列出該Mediator關(guān)心的Notification
override public function listNotificationInterests( ) : Array
{
return [
LoginProxy.LOGIN_FAILED,
LoginProxy.LOGIN_SUCCESS
];
}
// 處理Notification
override public function handleNotification( note:INotification ):void
{
switch ( note.getName() ) {
case LoginProxy.LOGIN_FAILED:
LoginPanel.loginVO = new LoginVO( );
loginPanel.loginStatus = LoginPanel.NOT_LOGGED_IN;
break;
case LoginProxy.LOGIN_SUCCESS:
loginPanel.loginStatus = LoginPanel.LOGGED_IN;
break;
}
}
// 戶單擊用Login鈕嘗試錄按,登。
private function onTryLogin ( event:Event ) : void {
sendNotification( ApplicationFacade.LOGIN, loginPanel.loginVO );
}
// 把viewComponent轉(zhuǎn)類化成它真正的型。
protected function get loginPanel() : LoginPanel {
return viewComponent as LoginPanel;
}
}
}
注意LoginPanelMediator在構(gòu)造方法中給LoginPanel注冊(cè)了一個(gè)偵聽(tīng)方法——onTryLogin,當(dāng)用戶單擊Login按鈕時(shí)這個(gè)方法會(huì)被執(zhí)行。在onTryLogin方法里發(fā)送了一個(gè)LOGIN的Notification(通知,攜帶參數(shù)LoginVO對(duì)象)。
早先(在ApplicationFacade中)我們已經(jīng)把LoginCommand注冊(cè)到這個(gè)Notification上了。LoginCommand會(huì)調(diào)用LoginProxy的“登錄”方法,傳參LoginVO。LoginProxy把“登錄”請(qǐng)求遠(yuǎn)程服務(wù),之后發(fā)送LOGIN_SUCCESS(登錄成功)或LOGIN_FAILED(登錄失?。┑腘otification。這些類的定義請(qǐng)參見(jiàn)“Proxy”章節(jié)。
LoginPanelMediator把LOGIN_SUCCESS和LOGIN_FAILED注冊(cè)自己的關(guān)心的Notification,當(dāng)這兩個(gè)Notification被發(fā)送時(shí),Mediaotr作為響應(yīng)把LoginPanel的loginStatus設(shè)置為L(zhǎng)OGGED_IN(登錄成功時(shí))或NOT_LOGGED_IN(登錄失敗時(shí)),并清除LoginVO對(duì)象。
Proxy
一般來(lái)說(shuō),Proxy Pattern(代理模式)被用來(lái)為控制、訪問(wèn)對(duì)象提供一個(gè)代理。在基于PureMVC的應(yīng)用程序,Proxy類被設(shè)計(jì)用來(lái)管理程序數(shù)據(jù)模型。
一個(gè)Proxy有可能管理對(duì)本地創(chuàng)建的數(shù)據(jù)結(jié)構(gòu)的訪問(wèn)。它是Proxy的數(shù)據(jù)對(duì)象。
在這種情況下,通常會(huì)以同步的方式取得或設(shè)置數(shù)據(jù)。Proxy可能會(huì)提供訪問(wèn)Data Object部分屬性或方法的API,也可能直接提供Data Object的引用。如果提供了更新Data Object的方法,那么在數(shù)據(jù)被修改時(shí)可能會(huì)發(fā)送一個(gè)Notifidation通知系統(tǒng)的其它部分。
Remote Proxy被用來(lái)封裝與遠(yuǎn)程服務(wù)的數(shù)據(jù)訪問(wèn)。Proxy維護(hù)那些與Remote service(遠(yuǎn)程服務(wù))通信的對(duì)象,并控制對(duì)這些數(shù)據(jù)的訪問(wèn)。
在這種情況下,調(diào)用Proxy獲取數(shù)據(jù)的方法,然后等待Proxy在收到遠(yuǎn)程服務(wù)的數(shù)據(jù)后發(fā)出異步Notification。
Proxy封裝了數(shù)據(jù)模型,管理Data Object及對(duì)Data Object的訪問(wèn),不管數(shù)據(jù)來(lái)自哪里,什么類型。
在PureMVC中,Proxy是個(gè)被Model注冊(cè)的簡(jiǎn)單的數(shù)據(jù)持有者。
雖然Proxy類已經(jīng)是完全可用的了,但是通常對(duì)于具體的應(yīng)用你應(yīng)該編寫(xiě)Proxy的子類,增加操作方法。
通常Proxy Pattern有以下幾種類型:
o
Remote Proxy, 當(dāng)Proxy管理的數(shù)據(jù)存放在遠(yuǎn)程終端,通過(guò)某種服務(wù)訪問(wèn)。
o
Proxy and Delegate, 多個(gè)Proxy共享對(duì)一個(gè)服務(wù)的訪問(wèn),由Delegate封裝對(duì)服務(wù)的控制訪問(wèn),確保響應(yīng)正確的返回給相應(yīng)的請(qǐng)求者。
o
Protection Proxy, 用于數(shù)據(jù)對(duì)象的訪問(wèn)有不同的權(quán)限時(shí)。
o
Virtual Proxy, 對(duì)創(chuàng)建開(kāi)銷很大的數(shù)據(jù)對(duì)象進(jìn)行管理。
o
Smart Proxy, 首次訪問(wèn)時(shí)載入數(shù)據(jù)對(duì)象到內(nèi)存,并計(jì)算它被引用的次數(shù),允許鎖定確保其他對(duì)象不能修改。
轉(zhuǎn)換數(shù)據(jù)對(duì)象
Proxy基類的構(gòu)造方法接受一個(gè)名稱(name)和一個(gè)Object類型的參數(shù),Object類型的參數(shù)用來(lái)設(shè)置Proxy管理的數(shù)據(jù)模型,在構(gòu)造方法完成后也可以調(diào)用
轉(zhuǎn)換數(shù)據(jù)對(duì)象
setData方法來(lái)設(shè)置。
就像Mediator和它的View Component一樣,為了訪問(wèn)它的屬性和方法,你會(huì)經(jīng)常需要把這個(gè)Data Object轉(zhuǎn)化成它真正的類型??雌饋?lái)只是重復(fù)繁瑣了一些,但更嚴(yán)重的是可能會(huì)暴露過(guò)多的Data Object細(xì)節(jié)。
另外,因?yàn)镈ata Object通常是一個(gè)復(fù)雜的數(shù)據(jù)結(jié)構(gòu),我們經(jīng)常需要引用它的一部分屬性并將類型轉(zhuǎn)化成我們需要的數(shù)據(jù)。
再一次的,ActionScript語(yǔ)言支持隱式的getter和setter屬性,它可以很好地幫助我們解決這種頻繁的類型轉(zhuǎn)換問(wèn)題。
一個(gè)很好的慣用做法是在你具體的Proxy類中引入一個(gè)適當(dāng)命名的隱式getter,用來(lái)把Data Object轉(zhuǎn)化它真正的類型。
另外,可能需要定義不同的多個(gè)類型getter來(lái)取得Data Object某部分的數(shù)據(jù)。
比如:
public function get searchResultAC () : ArrayCollection
{
return data as ArrayCollection;
}
public function get resultEntry( index:int ) : SearchResultVO
{
return searchResultAC.getItemAt( index ) as SearchResultVO;
}
在別的Mediator中,我們不用再這樣做了:
var item:SearchResultVO =
ArrayCollection ( searchProxy.getData() ).lastResult.getItemAt( 1 ) as SearchResultVO;
而可以這樣:
var item:SearchResultVO = searchProxy.resultEntry( 1 );
避免對(duì)Mediator的依賴
Proxy不監(jiān)聽(tīng)Notification,也永遠(yuǎn)不會(huì)被通知,因?yàn)镻roxy并不關(guān)心View的狀態(tài)。但是,Proxy提供方法和屬性讓其它角色更新數(shù)據(jù)。
Proxy對(duì)象不應(yīng)該通過(guò)引用、操作Mediator對(duì)象來(lái)通知系統(tǒng)它的Data Object(數(shù)據(jù)對(duì)象)發(fā)生了改變。
它應(yīng)該采取的方式是發(fā)送Notification(這些Notification可能被Command或Mediator響應(yīng))。Proxy不關(guān)心這些Notification被發(fā)出后會(huì)影響到系統(tǒng)的什么。
把Model層和系統(tǒng)操作隔離開(kāi)來(lái),這樣當(dāng)View層和Controller層被重構(gòu)時(shí)就不會(huì)影響到Model層。
但反過(guò)來(lái)就不是這樣了:Model層的改變很難不影響到View層和Controller層。畢竟,它們存在的目的就是讓用戶與Model層交互的。
Model層中的改變總會(huì)造成View/Controller層的一些重構(gòu)。
我們把Domain Logic(域邏輯)盡可能放在Proxy中實(shí)現(xiàn),這樣盡可能地做到Model層與相關(guān)聯(lián)的View層、Controller層的分離。
Proxy僅僅對(duì)訪問(wèn)對(duì)不用來(lái)管理數(shù)據(jù)象的,而且用來(lái)封裝數(shù)據(jù)象的操作使得維態(tài)數(shù)據(jù)持在一個(gè)合法的狀。
比如,算稅是一個(gè)計(jì)營(yíng)業(yè)(域邏輯),它放在應(yīng)該中Domain LogicProxy實(shí)現(xiàn)而不是Mediator或Command。
雖然可以放在任意一個(gè)中實(shí)現(xiàn),但是把它放在Proxy中實(shí)現(xiàn)不僅僅是出于邏輯(logic)上的考慮,這樣做還可以保持其它層更輕便、更易被重構(gòu)。
一個(gè)Mdeiator可能獲取(retrieve)這個(gè)Proxy對(duì)象;調(diào)用它的營(yíng)業(yè)稅計(jì)算方法,傳參一些表單項(xiàng)目數(shù)據(jù)。如果把真正的計(jì)算放在Mediator的話,就是把Domain Logic(域邏輯)嵌在View層了。對(duì)營(yíng)業(yè)稅的計(jì)算是Domain Model(域模型)中的一條規(guī)則。View僅僅是把它看成Domain Model的一個(gè)屬性,當(dāng)用戶的輸入正確這個(gè)屬性對(duì)View就可用。
假設(shè)你現(xiàn)在正在工作的程序項(xiàng)目是一個(gè)嵌在瀏覽器中桌面級(jí)的RIA解決方案。但新的版本可能是簡(jiǎn)化了用例(use case)的嵌在PDA中的解決方案,但仍然完全需要當(dāng)前程序項(xiàng)目的Model層。
如果已經(jīng)有正確的分離操作,我們就可以完全重用Model層而只需開(kāi)發(fā)新的View層和Controller層。
雖然把對(duì)營(yíng)業(yè)稅的計(jì)算放在Mediator上看起來(lái)很有效而且在寫(xiě)代碼時(shí)也很容易;你可能只需要把營(yíng)業(yè)稅計(jì)算出來(lái)交給Model而已。
然而你卻需要在程序的不同版本中重復(fù)的付出,在每個(gè)新的View層復(fù)制粘貼營(yíng)業(yè)稅計(jì)算邏輯,所以最好把這段邏輯放在Model層。
Remote Proxy對(duì)象是一個(gè)從遠(yuǎn)程位置(Remote location)獲取Data Object的Proxy。這通常意味著我們與它的交互是以異步的方式。
Proxy獲取數(shù)據(jù)的方式取決于客戶端平臺(tái)、遠(yuǎn)程服務(wù)(remote service)的實(shí)現(xiàn)和開(kāi)發(fā)人員的選擇。在Flash/Flex環(huán)境中,我們可能會(huì)使用HTTPService,WebService,RemoteObject,DataService或者XMLSocket來(lái)從Proxy中發(fā)送服務(wù)請(qǐng)求。
根據(jù)需要,Remote Proxy可能動(dòng)態(tài)的發(fā)送請(qǐng)求,響應(yīng)時(shí)會(huì)設(shè)置一個(gè)屬性或調(diào)用一個(gè)方法; 或只在構(gòu)造方法中發(fā)送一次請(qǐng)求,然后提供訪問(wèn)數(shù)據(jù)的get/set方法。
在Proxy中有很多東西可以優(yōu)化以提高與遠(yuǎn)程服務(wù)通信的效率。
比如:緩存(服務(wù)器返回的)數(shù)據(jù)以減少網(wǎng)絡(luò)通信的“廢話”;只發(fā)送改變的數(shù)據(jù),減少帶寬的浪費(fèi)。
如果請(qǐng)求是由系統(tǒng)其它角色動(dòng)態(tài)調(diào)用Remote Proxy的方法而發(fā)出的,那Proxy在結(jié)果返回來(lái)時(shí)應(yīng)該發(fā)送一個(gè)Notification。
注意關(guān)心這個(gè)Notification的角色有可能并不是發(fā)起這個(gè)數(shù)據(jù)請(qǐng)求的那個(gè)角色。
舉例,調(diào)用遠(yuǎn)程服務(wù)的查詢并顯示返回的結(jié)果,這個(gè)過(guò)程通常會(huì)有以下幾步:
o
一個(gè)View Component觸發(fā)一個(gè)事件發(fā)起一個(gè)查詢請(qǐng)求。
o
它的Mediator響應(yīng):獲取相應(yīng)的RemoteProxy,設(shè)置它的searchCriteria 屬性。
o
Proxy的searchCriteria屬性其實(shí)是一個(gè)隱式setter,它會(huì)保存賦值,通過(guò)內(nèi)部的HTTPService(它會(huì)偵聽(tīng)result和fault事件)初始查詢請(qǐng)求。
o
當(dāng)服務(wù)返回結(jié)果時(shí),HTTPService會(huì)觸發(fā)ResultEvent事件,Proxy響應(yīng),把結(jié)果保存在公共屬性中。
o
Proxy然后發(fā)送一個(gè)Notification表示請(qǐng)求成功,這個(gè)Notification會(huì)綁定一個(gè)對(duì)數(shù)據(jù)對(duì)象的引用作為“報(bào)體”。
o
關(guān)心這個(gè)Notification的另外一個(gè)Mediator就會(huì)響應(yīng)這個(gè)Notification,把“報(bào)體”中的數(shù)據(jù)賦值給它所操作的View Component的dataProvider屬性。
再來(lái),假設(shè)有一個(gè)LoginProxy,它有一個(gè)LoginVO(一個(gè)Value Object;簡(jiǎn)單的數(shù)據(jù)載體類)。LoginVO可能看起來(lái)像這樣:
package com.me.myapp.model.vo
{
//把這個(gè)AS3 VO映射到Remote Class
[RemoteClass(alias="com.me.myapp.model.vo.LoginVO")]
[Bindable]
public class LoginVO
{
public var username: String;
public var password: String;
public var authToken: String;//登錄權(quán)限允許時(shí)服務(wù)器設(shè)置此值
}
}
LoginProxy對(duì)設(shè)錄錄外提供方法置登數(shù)據(jù),登(logging in),退出(logging out獲),取“錄登”時(shí)權(quán)標(biāo)識(shí)用到的限。
LoginProxy:
package com.me.myapp.model
{
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;
import mx.rpc.remoting.RemoteObject;
import org.puremvc.as3.interfaces.*;
import org.puremvc.as3.patterns.proxy.Proxy;
import com.me.myapp.model.vo.LoginVO;
// 用于用戶登錄的Proxy
public class LoginProxy extends Proxy implements IProxy {
public static const NAME:String = 'LoginProxy';
public static const LOGIN_SUCCESS:String = 'loginSuccess';
public static const LOGIN_FAILED:String = 'loginFailed';
public static const LOGGED_OUT:String = 'loggedOut';
private var loginService: RemoteObject;
public function LoginProxy () {
super( NAME, new LoginVO ( ) );
loginService = new RemoteObject();
loginService.source = "LoginService";
loginService.destination = "GenericDestination";
loginService.addEventListener( FaultEvent.FAULT, onFault );
loginService.login.addEventListener( ResultEvent.RESULT, onResult );
}
// 隱式getter,轉(zhuǎn)化data的類型
public function get loginVO( ) : LoginVO {
return data as LoginVO;
}
//如果logivVO中包含了authToken(授權(quán)標(biāo)識(shí))表示用戶登錄成功
public function get loggedIn():Boolean {
return ( authToken != null );
}
// 取得authToken
public function get authToken():String {
return loginVO.authToken;
}
//置用的限設(shè)戶權(quán)標(biāo)識(shí),登錄,退出,或登繼續(xù)嘗試錄。
public login( tryLogin:LoginVO ) : void {
if ( ! loggedIn ) {
loginVO.username= tryLogin.username;
loginVO.password = tryLogin.password;
} else {
logout();
login( tryLogin );
}
}
// 退出,地清空簡(jiǎn)單LoginVO
public function logout( ) : void
{
if ( loggedIn ) loginVO = new LoginVO( );
sendNotification( LOGGED_OUT );
}
//通知系登成功統(tǒng)錄
private function onResult( event:ResultEvent ) : void
{
setData( event.result ); // immediately available as loginVO
sendNotification( LOGIN_SUCCESS, authToken );
}
//通知系登失統(tǒng)錄敗
private function onFault( event:FaultEvent) : void
{
sendNotification( LOGIN_FAILED, event.fault.faultString );
}
}
}
一個(gè)LoginCommand會(huì)獲取LoginProxy,設(shè)置登錄的數(shù)據(jù),調(diào)用登錄函數(shù),呼叫登錄服務(wù)。
接下來(lái),可能一個(gè)GetPrefsCommand會(huì)響應(yīng)LOGIN_SUCCESS(登錄成功)這個(gè)Notification,從Notificaiton的“報(bào)體”中獲取authToken(授權(quán)標(biāo)識(shí)),接著呼叫下一個(gè)服務(wù),獲取用戶的(比如)配置信息(preferences)。
LoginCommand:
package com.me.myapp.controller {
import org.puremvc.as3.interfaces.*;
import org.puremvc.as3.patterns.command.*;
import com.me.myapp.model.LoginProxy;
import com.me.myapp.model.vo.LoginVO;
public class LoginCommand extends SimpleCommand {
override public function execute( note: INotification ) : void {
var loginVO : LoginVO = note.getBody() as LoginVO;
var loginProxy: LoginProxy;
loginProxy = facade.retrieveProxy( LoginProxy.NAME ) as LoginProxy;
loginProxy.login( loginVO );
}
}
}
GetPrefsCommand:
package com.me.myapp.controller {
import org.puremvc.as3.interfaces.*;
import org.puremvc.as3.patterns.command.*;
import com.me.myapp.model.LoginProxy;
import com.me.myapp.model.vo.LoginVO;
public class GetPrefsCommand extends SimpleCommand {
override public function execute( note: INotification ) : void {
var authToken : String = note.getBody() as String;
var prefsProxy : PrefsProxy;
prefsProxy = facade.retrieveProxy( PrefsProxy.NAME ) as PrefsProxy;
prefsProxy.getPrefs( authToken );
}
}
}