1. 程式人生 > >(2012-4-12 老物搬運)如何去寫mediator

(2012-4-12 老物搬運)如何去寫mediator

Mediating your view components

Mediators在應用程式中扮演著檢視元件和其餘部分的溝通橋樑的角色。它們讓你的檢視元件擺脫了對事務邏輯和域邏輯處理的任務,這些任務適合讓其他層來完成。Mediator應當是輕量級的,並且作為一個橋樑保持著與其他部分的緊密注視的同時又具有最小的依賴性。

Mediators are created when yourview hits the stage

MediatorMap 會對你提供的作為contextView的DisplayObjectContainer物件進行偵聽Event.ADDED_TO_STAGE 和 Event.REMOVED_FROM_STAGE。每當一個DisplayObject被新增或者移除,基於你之前進行的mediator對映,mediatormap就會檢查它是否需要建立或者銷燬一個相關聯的Mediator。

Avoid logic in your Mediators

對每個新手來說,mediator 對映大概是非常容易讓人上癮並且是Robotlegs裡最容易讓人快速接受的部分。但目前為止它也是最危險的部分。它提供的一個方法可以讓你將view和application連線起來,與此同時view自身卻對它所處的app環境毫不知情,假如使用得當那麼它確實是一個聯通應用程式中2層之間非常優秀的橋樑。

而危險在於,由於mediator也可以讓model和service被自由地注入,你可能會濫用mediator去創造一個“快速又適用的”的怪物般的控制器(controller),那麼這傢伙很快就會長成一個緊密耦合的邏輯混亂的一團糟,你會在其他地方複製它,而當你要對你的類作出修改的時候它又變得非常脆弱。

你需要儘可能地在你的mediator中避免邏輯程式碼。Mediator不是用來處理應用程式的決策的。它們只是作為一個view與其他部分的橋樑。你必須儘可能地在你的mediator裡避免使用switchforfor each或者if這樣的語法。可能有時無法避免,你必須使用這樣一個邏輯塊去完成你的工作。這是一個危險的符號:停下來並且思考一下這個邏輯塊到底該屬於哪一部分?這種決策是否合適在view裡進行?既然Command是用來組織資料並傳遞給view的,那麼我可不可以把它放到Command裡去?既然Model是用來通知Mediator的,那麼這塊域邏輯是否應該屬於Model?

記住Mediator不是你的檢視層,你才可以避免這種問題的發生。Mediator只是位於檢視元件和其他成員的中間,它經常會試著深入view並且進行一些複雜操作——比如new並add一些新的children、影響檢視佈局、或者直接操作屬性。要完成這些操作,你應當在被對映的

view上面定義一些API並且在Mediator中呼叫API,而不是讓Mediator自己去完成全部工作

Mediator是view與其他成員的黏合劑,但事實上不是他們之中任何一員。假如你真的懷疑你自己,那麼在往Mediator裡寫程式碼時,問問自己:“這是view程式碼?app程式碼?還是合劑程式碼?”,你要堅持——只有黏合劑程式碼可以放在這。

Do your wiring inonRegister()

如果你建立了mediator並且將view注入到了mediator,並且mediatorMap認為view 已經就緒(非FLEX的view會立刻就緒,Flex檢視會在creationComplete事件後就緒),那麼MediatorMap 就會在你的Mediator中執行一個叫做onRegister的函式,這個函式需要你在你自己的mediator類中覆寫使得你的佈局啟動。

override public function onRegister():void

{

   //我們在這兒獲取使用者的行為並且翻譯這些行為告訴應用程式

  addViewListener(MouseEvent.CLICK, submitUserDetails, MouseEvent);

   //我們在這兒獲取應用程式的事件並且翻譯這些事件告訴view

  addContextListener(UserEvent.LOGIN_FAILED, showErrors, UserEvent);

【在Flex應用程式中,mediator不會訪問元件物件生命迴圈的初始化方法。MediatorMap會等待這個view廣播creationComplete事件,然後再執行onRegister()方法】

警告

注意:1fp9.0.16不會廣播Event.ADDED_TO_STAGE或者Event.REMOVED_FROM_STAGE。因此,mediatormap無法得知view是否已經被放置到contextView中,所以也不會自動地建立/銷燬mediator

2Flash時間軸也很古怪——當view在時間軸動畫中來來去去時,這些事件有時也不會被廣播。

這些問題都有替代辦法的,來我們的論壇你就會找到適合你的問題的解決辦法。

 

Mediator只有非常侷限的活動範圍,事實上它只做2件事——偵聽來自view 的事件然後把它們轉換成用於轉發的應用程式事件並且與其他dispatcher共享這些事件;偵聽來自應用程式其他成員的事件然後轉換成某種行為去操作view 的API。

你不應該直接在view上註冊它自己發出的偵聽(它的子物件發出的除外)或者同享事件dispatcher——作為替代,你應當使用mediator所擁有的一個eventMap屬性,它可以更方便地更可靠地關注事件偵聽。

eventMap.mapListener(dispatcher:IEventDispatcher, type:String,listener:Function, eventClass:Class = null, useCapture:Boolean = false,priority:int = 0, useWeakReference:Boolean = true):void

使用addViewListener()和addContextListener()方法其實也是在使用eventMap的這個方法,只不過比較方便。它的第四個引數eventClass最好設定一下,否則在傳遞事件的時候是預設使用Event類的。

大部分的清理工作都會由robotlegs自己完成,如果你一定有一些東西要手動處理去防止記憶體洩露,那麼你就去覆寫onRemove函式吧。

Why can’t Mediators be injected into other objects?

在robotlegs1.6裡,我們已經從injector上完全移除了mediator的對映,所以一般來說你無法注入一個mediator到另一個類,除非你已經自己手動設定過了注入(假如你想這麼做,來論壇和我們談談,因為這種做法是一個可行但不是最好最有效的解決手段)。但是早期版本,鑑於mediatormap執行工作的副作用,你是可以注入mediator的。

不要注入mediator的第一理由就是它有可能在view從舞臺移除後依然存在,臥槽!——你會突然發現記憶體洩露並且滿地奇怪的現象。

但是從一個更加理論的角度,mediator除了那些被覆寫的方法(一般只有onRegister和onRemove)以外,不應當有任何API。作為一個“黏合劑程式碼”的角色,其他任何物件都不應該使用或操作它們,總而言之——mediator對於其他物件沒有任何用處,要來也沒用。

通常,當人們討論注入mediator的時候,他們都是在擔心自定義事件的繁殖,並且似乎是想要通過幾個mediator之間直接呼叫來避免事件的繁殖。Event是很廉價的,也很容易建立(如果你否認這一點,那麼你大概要換一個新的IDE了),還可以讓你的程式解耦。擁抱這種又多又小的類吧,你將獲得回報的。你要做的只是把事件的名稱命名的更加具有描述性——“我不喜歡一坨這種小小的類,它們很混亂而且我不知道去哪裡尋找程式碼”這樣的問題其實都是類和事件的命名太爛。

好的事件名稱

爛的

DesignCreationEvent.DESIGN_CREATED

CreationEvent.CREATED

UserModelEvent.USER_SAVED

ModelEvent.SAVED

StatsServiceEvent.SUBMISSION_COMPLETED

ServiceEvent.SUCCESS

Working with complex composite views

如果要給一個複合元件的某個子物件新增偵聽,比如一個元件有2個按鈕,你可以像這麼寫:

[元件的mediator]

override public function onRegister():void

{

    eventMap.mapListener(view.deleteButton, MouseEvent.CLICK,  dispatchDeletionRequest, MouseEvent);

    eventMap.mapListener(view.radioButton, MouseEvent.CLICK,  dispatchSupplySelected, MouseEvent);

}

但這很不優雅,事實上你應當在元件的內部對子物件新增偵聽,然後由元件自身傳送一個自定義事件:

[元件的mxml]

private function deleteTaskHandler(event:Event):void

{

    dispatchEvent(newDeleteTaskEvent(taskList.selectedItem as Task));

}

[元件的mediator]

override public function onRegister():void

{

    eventMap.mapListener(view, DeleteTaskEvent.DELETE,  dispatchDeletionRequest, MouseEvent);

    eventMap.mapListener(view, SelectTaskEvent. SELECT,  dispatchSupplySelected, MouseEvent);

}

提示:另一種解決的辦法是使用AS3Signals類庫,它非常擅長在AS事件系統中進行轉換,由Robert Penner發明。你可以在強化章節:在複合View與它們的Mediator之間使用Signals。

Using the same mediator with more than one view

有些時候,你可能想讓2個view以同樣的方式被中介。也許是因為其中一個view繼承了另外一個。一個典型的例子就是一個套嵌選單,這個選單的選項和子選項可能有不同的表現和內部行為,但是在相對於應用程式的關係上又表現的一致。當用戶劃過或者點選某個選項/子選項,你想要讓mediator傳送一個事件通知應用程式某個選項被劃過/點選了,所以你想要把這2個view 都對映到同一個mediator上。

有趣的是,mediator希望view作為一個確切的型別被注入。假如mediator希望SectionButton被注入,那麼當SubSectionButton的mediator被建立的時候,它會抱怨說” null injection error”。這是因為mediatorMap會根據view 的全飾類名(FQCN)自動注入view,而不會自動注入它的超類或者介面。解決辦法就是對你想要對映的額外的類進行指定。

Example 8-6. 要針對一個子類或者介面複用一個mediator,你需要在對映的時候指定這個類去注入

mediatorMap.mapView(SectionButton, SectionButtonMediator);

// this will ensure the SubSectionButton is mapped against

// the SectionButton class that the mediator expects

// this 3rd parameter - 'injectViewAs' - can be a Class or an arrayof Classes

mediatorMap.mapView(SubSectionButton, SectionButtonMediator,SectionButton);

另外一種工作流程是為子view類拓展一個基礎mediator類——也許你的SubSectionButton需要一些額外的行為,所以你想要拓展SectionButtonMediator去創造一個SubSectionButtonMediator並新增這些行為。在你使用SubSectionButton獨有的那些API的時候,你需要將這個view作為SectionButton注入以便履行基礎SectionButtonMediator的注入,同時又作為SubSectionButton注入:

mediatorMap.mapView(SectionButton, SectionButtonMediator);

// this will ensure the SubSectionButton is mapped against

// the SectionButton class that the superclass mediator expects

// and against the SubSectionButton class that the subclass mediatorexpects

mediatorMap.mapView(SubSectionButton, SubSectionButtonMediator,[SectionButton, SubSectionButton]);

A good Mediator is just a mailman

下面這些現象預示著你的mediator已經越權了:

u  實現檢視邏輯或者檢視控制,比如實現動畫或者驗證使用者輸入

u  深入view並且直接操作其屬性、其子元件,而不是使用view 的API

u  Mediator的用它自己的屬性去儲存view的某些行為狀態

u  在抉擇呼叫哪個API之前使用switch這類語句去檢查view 的屬性

u  在派發事件之前使用switch這類語句去檢查view 的屬性

u  注入其他物件以便在條件語句中使用它的屬性

u  使用複雜的條件語句選擇event的屬性(比如檢查一個ID來判斷這條message是否屬於你的view應該是它能夠得到的最遠之處了)

u  注入超過一個view

u  注入其他mediator

保證你的mediator足夠懶,記住一個你印象裡最懶的一個人,你在寫mediator的時候就想著他推脫責任的那些說辭。

Signs that you need anothermediator

何時該把一些子元件的mediator分出來獨立給它寫一個mediator?如果你的mediator偵聽了超過6個事件,你就該考慮這個問題了,如果超過了12個事件,你應該把這個mediator分成幾個小的mediator。

Never put view logic into themediator.

永遠也不要把檢視邏輯放到mediator裡。Mediator只需要翻譯(黏合)邏輯。檢視邏輯包括驗證input、實現view及其子view的動畫。如果你需要一個脫離view自身的檢視邏輯,你應該使用三層的手段實現。