Robotlegs最佳實踐

Documentation for Robotlegs v1.0RC1

目錄

  1. Robotlegs 是什麼
  2. 依賴注入
  3. 使用 Injectors
  4. The Context
  5. MVCS 參考實現
    1. Context
    2. Controller & Commands
    3. View & Mediators
    4. Model, Service and the Actor
    5. Model
    6. Service
    7. 框架事件
    8. Commands
      1. Command 職責
      2. 觸發 Command
      3. 鏈接 Command
      4. 應用程序層的解耦
    9. Mediators
      1. Mediator 職責
      2. 映射一個 Mediator
      3. View Component 的自動中介
      4. View Component 的手動中介
      5. 映射主程序 (contextView) Mediator
      6. 訪問一個 Mediator 的 View Component
      7. 給一個 Mediator 添加事件監聽
      8. 監聽框架事件
      9. 廣播框架事件
      10. 監聽 View Component 事件
      11. 通過 Mediator 訪問 Model 和 Service
      12. 訪問其它 Mediator
    10. Models
      1. Model 職責
      2. 映射一個 Model
      3. 從一個Model裏廣播事件
      4. 在一個 Model 裏監聽框架事件
    11. Services
      1. Service 職責
      2. 映射一個 Service
      3. 在一個 Service 裏監聽框架事件
      4. 廣播框架事件
      5. Service 示例

Robotlegs 是什麼

Robotlegs 是一個用來開發Flash, Flex, 和 AIR 應用的純 AS3 微架構(框架). Robotlegs 專注於將應用程序各層排布在一起並提供它們相互通訊的機制. Robotlegs 試圖通過提供一種解決常見開發問題的經過時間檢驗的架構解決方案來加速開發. Robotlegs 無意鎖定你到框架, 你的類就是你的類的樣子, 而且應該很容易地切換到其他框架.

框架提供一個基於 Model-View-Controller 元設計模式的默認實現. 這個實現提供一個針對應用程序結構和設計的強烈建議. 雖然它確實輕微減低了你的應用程序的便攜性, 不過它依然以最低限度影響你的具體類爲目標. 通過擴展MVCS 實現類, 你可以獲得很多有用的方法和屬性.

你不必使用Robotlegs的標準 MVCS 實現.你可以使用它的任意部分, 或者完全不使用它, 或者使用自己的實現來適應你的需求. 它是爲了提供合適的參考實現和快速開始使用 Robotlegs 而被包含進來。

依賴注入

Robotlegs 圍繞 依賴注入 設計模式展開.

最簡單地, 依賴注入是爲對象提供實例變量或屬性的行爲. 當你傳遞一個變量到一個類的構造函數, 你在使用依賴注入. 當你設置一個類的屬性, 你在使用依賴注入. 如果你不是使用嚴格的過程或線性方式編寫AS3, 很可能你現在就在使用依賴注入。

Robotlegs 使用基於元數據的自動依賴注入. 這是爲了方便開發而提供, 而且在排布應用程序並提供類和它所需要的依賴時,可以減少很多代碼量. 雖然完全可以手動提供這些依賴, 但是允許框架來履行這些職責可以減少出錯的機會,並且通常可以加快編碼進程。.

使用 Injectors

Robotlegs 採用一種適配器(adapter)機制來爲框架提供依賴注入機制. 默認地, 框架配備了 SwiftSuspenders 注入/反射庫來適合這個要求. 另有 SmartyPants-IoC 和 Spring Actionscript 的適配器可以使用. 可能有潛在的特定需求來使用其它的依賴注入適配器, 但是如果沒有特別的理由, 建議使用默認的SwiftSuspenders, 因爲它爲 Robotlegs 做了一些特別調整.

SwiftSuspenders 適配器注入語法

SwiftSuspenders 支持三種類型的依賴注入

  • 屬性(域)注入
  • 參數(方法/設值) 注入
  • 構造注入

鑑於此文檔的目的, 我們將特別介紹屬性注入, 以及在 Robotlegs 裏如何使用. 將屬性注入類有兩種選擇. 你可以使用未命名,或命名的注入:

[Inject]
public var myDependency:Depedency; //未命名注入
[Inject(name="myNamedDependency")]
public var myNamedDependency:NamedDepedency; //命名注入

Robotlegs 裏三處提供了注入映射. MediatorMap, CommandMap, 和直接通過 Injector . MediatorMap 和 CommandMap 也都是使用 Injector, 但它們同時做了一些各自層(tier)所需要的額外工作. 顧名思義, MediatorMap 用來映射 Mediator, CommandMap 用來映射 Command, 其它所有需要被注入的內容 (包括但不限於 Model) 都要直接使用 Injector 映射.

Injector 類的映射注入

具體 Injector 類的適配器都遵照 IInjector 接口. 這個接口爲不同的依賴注入解決方案提供了統一的API. 本文檔專注於 SwiftSuspenders, 但這些語法同樣適應於其它任何遵照 Iinjector 接口的 Injector.

injector 是你應用程序裏所發生的所有依賴注入的生產車間. 它用來注入框架 actor, 同時也可以用來執行你的應用程序所需要的任何其它注入. 這包括但不限於 RemoteObjects, HTTPServices, 工廠類, 或者事實上任何有可能成爲你的對象所需要的依賴的類/接口.

下面是實現 IInjector 接口的類所提供的四個映射方法:

mapValue

mapValue 用來映射一個對象的特定實例到一個 injector. 當請求一個特定的類,使用類的這個特定實例來注入.

//你的應用程序中某個映射/配置發生的地方
var myClassInstance:MyClass = new MyClass();
injector.mapValue(MyClass, myClassInstance);
//在接收注入的類中
[Inject]
public var myClassInstance:MyClass
mapValue(whenAskedFor:Class, instantiateClass:Class, named:String = null)

MyClass 的實例被創建和保留,並在被請求的時候被注入. 當此類被請求的時候,這個實例被用來滿足這個注入請求. 請注意很重要的一點,因爲你已經手動創建並通過 mapValue 映射了一個類實例, 這個實例所需要的依賴將不會被自動注入. 你需要手動或通過 injector 注入這些依賴:

injector.injectInto(myClassInstance);

這將立即爲此實例提供已映射的可以注入的屬性。.

mapClass

mapClass 爲每一個注入請求提供這個被映射的類的一個 特有( unique) 實例.

//你的應用程序中某個映射/配置發生的地方
injector.mapClass(MyClass);
//在第一個接收注入的類裏
[Inject]
public var myClassInstance:MyClass
//在第二個接收注入的類裏
[Inject]
public var myClassInstance:MyClass

爲上面的每一個注入提供MyClass的 特有( unique) 實例來完成請求.

mapClass(whenAskedFor:Class, named:String = null)

injector 提供了一個方法來實例化被映射的對象:

injector.mapClass(MyClass);
var myClassInstance:MyClass = injector.instantiate(MyClass);

這提供你的對象的一個實例,並填充了此對象包含的所有被映射的注入點(injection points).

mapSingleton

mapSingleton 爲每一個注入請求提供類的一個 單一_(_single) 實例. 爲所有映射提供類的單一實例確保你維護一個一致的狀態並且不用擔心創建被此類的多餘實例. 這是一個被框架強制和管理的單一實例, 而不是一個在類內部強制的單例(Singleton).

//你的應用程序中某個映射/配置發生的地方
injector.mapSingleton(MyClass);
//在第一個接收注入的類裏
[Inject]
public var myClassInstance:MyClass
//在第二個接收注入的類裏
[Inject]
public var myClassInstance:MyClass

在上面的例子裏, 兩個注入請求都將由被請求類的相同實例填充. 這個注入是被延遲的, 即對象直到第一次被請求才被實例化.

mapSingletonOf(whenAskedFor:Class, useSingletonOf:Class, named:String = null)

mapSingletonOf

mapSingletonOf在功能上非常像 mapSingleton. 它對映射抽象類和接口很有用, 而 mapSingleton 用來映射具體類實現.

//你的應用程序中某個映射/配置發生的地方
injector.mapSingletonOf(IMyClass, MyClass); //MyClass implements IMyClass
//在第一個接收注入的類裏
[Inject]
public var myClassInstance:IMyClass
//在第二個接收注入的類裏
[Inject]
public var myClassInstance:IMyClass

這個注入方法對創建使用多態的更具可測性的類非常有用. 在下面的 Service 實例 章節可以找到一個例子.

MediatorMap 類的依賴注入

MediatorMap 實現 IMediatorMap 接口. IMediatorMap 提供兩個方法來將你的 mediators 映射到 view 並註冊它們以便用來注入.

mapView(viewClassOrName:*, mediatorClass:Class, injectViewAs:Class = null, autoCreate:Boolean = true, autoRemove:Boolean = true):void

mapView 接受一個視圖類, MyAwesomeWidget, 或者一個視圖的類全名, com.me.app.view.components::MyAwesomeWidget 作爲第一個參數. 第二個參數是將要作爲視圖組件中介的 Mediator 類.[injectViewAs 內容未完成], 最後的兩個參數 autoCreate 和 autoRemove 提供方便的自動管理 mediator 的布爾值開關.

//在你的程序裏某個映射/配置發生的地方
mediatorMap.mapView(MyAwesomeWidget, MyAwesomeWidgetMediator); 
//在conntextView 的顯示列表裏的某個地方
var myAwesomeWidget:MyAwesomeWidget = new MyAwesomeWidget();
this.addChild(myAwesomeWidget); // ADDED_TO_STAGE 事件被拋出, 觸發這個視圖組件的中介機制

這個方法使用了自動中介機制. 手動中介,以及對此方面更深入的內容將稍後在 Mediators 章節中介紹.

CommandMap 類的依賴注入

CommandMap 類實現 ICommandMap 接口, 提供一個用來將 command 映射到到觸發它們的框架事件的方法.

mapEvent(eventType:String, commandClass:Class, eventClass:Class = null, oneshot:Boolean = false)

你要提供給 commandMap 一個可以執行的類, 執行它的事件類型, 可選的這個事件的強類型, 以及這個 command 是否只被執行一次並且隨即取消映射的布爾值開關.

這個可選的強類型事件類用來對Flash平臺的"magic string"事件類型系統做額外的保護.以避免採用相同事件類型字符串的不同事件類之間的衝突.

//在你的程序裏某個映射/配置發生的地方
commandMap.mapEvent(MyAppDataEvent.DATA_WAS_RECEIVED, MyCoolCommand, MyAppDataEvent); 
//在事件被廣播的另外一個框架actor裏
//這觸發了隨後被執行的被映射的 command
dispatch(new MyAppDataEvent(MyAppDataEvent.DATA_WAS_RECEIVED, someTypedPayload))

The Context

Context 是所有 Robotlegs 具體實現的中心. 一個 Context, 或者也許多個 Context, 提供其它層進行通訊的機制. 一個應用程序並非只能使用一個 Context, 但大多情況下一個 Context 就足夠了. 如果在 Flash 平臺上創建模塊化應用程序, 多個 Context 就是必須的了. Context 在一個應用程序裏有三個功能: 提供初始化,[de-initialization – 非初始化?], 和用來通訊的事件中心bus.

package org.robotlegs.examples.bootstrap
{
	import flash.display.DisplayObjectContainer;
	
	import org.robotlegs.base.ContextEvent;
	import org.robotlegs.core.IContext;
	import org.robotlegs.mvcs.Context;
	
	public class ExampleContext extends Context implements IContext
	{
		public function UnionChatContext(contextView:DisplayObjectContainer)
		{
			super(contextView);
		}
		
		override public function startup():void
		{
			//這個 Context 只映射一個 command 到 ContextEvent.STARTUP 事件. 
			//這個 StartupCommand 將映射其它將在應用程序裏使用的的 command, 
			//mediator, service, 和 model.
			commandMap.mapEvent( ContextEvent.STARTUP, StartupCommand, ContextEvent, true );
						
			//啓動應用程序 (觸發 StartupCommand)
			dispatch(new ContextEvent(ContextEvent.STARTUP));
		}
	}
}

MVCS 參考實現

Robotlegs 裝備了一個參考實現. 這個實現遵照經典的 Model-View-Controller (MVC) 元設計模式, 另外增加了第四個叫做 Service 的 actor. 這些層在本文檔中通稱爲"核心 actor", 或者簡稱爲"actor".

MVCS 提供一個應用程序的框架概況. 通過將幾個經過時間檢驗的設計模式整合到一個具體實現, Robotlegs 的 MVCS 實現可以用做創建你的應用程序的一致方案. 通過這些架構概念着手一個應用程序, 你甚至可以在開始你的設計之前避免很多常見的障礙:

  • 分離
  • 組織
  • 解耦

分離

MVCS 提供一種將你的應用程序分離到提供特定功能的無關聯的層的很自然的方法. view 層處理用戶交互. model 層處理用戶創建的或從外部獲取的數據. controller 提供一種封裝各層之間複雜交互的機制. 最後, service 層提供一種和外界(比如遠程服務 API 或文件系統)交互的獨立機制.

組織

通過這種分離我們自然獲得一個組織水平. 每個項目都需要某個組織水平. 是的, 有人可以把他們所有的類都扔到頂級包下完事, 但即使是最小的項目這也是不可接受的. 當一個項目有了一定的規模就需要開始組織類文件的結構了. 當向同一個應用程序開發中增加團隊成員的時候問題就更加嚴重了. RobotLegs 的 MVCS 實現爲項目描繪出一個分爲四層的優雅的組織結構.

解耦

Robotlegs 的MVCS實現將應用程序解耦爲4層. 每層都與其它層隔離, 使分離類和組件分別測試變得非常容易. 除了簡化測試進程, 通常也使類更具便攜性以在其它項目中使用. 比如, 一個連接到遠程 API 的 Service 類可能在多個項目中都很有用. 通過解耦這個類, 它可以不需重構便從一個項目轉移到另一箇中使用.

這個默認實現只是充作最佳實踐建議的一個例子. Robotlegs 並不打算以任何方式束縛你到這個例子, 它只是一個建議. 你可以隨意開發自己的實現來適應你喜歡的命名規範和開發需求. 如果這正是你所追求的, 請一定告訴我們, 因爲我們一直對新方案很感興趣, 而且它有可能作爲一種替代實現包含到 RobotLegs 的代碼倉庫中.

Context

像 RobotLegs 中的其它實現一樣, MVCS 實現也是圍繞一個或多個 Context. 這個 context 提供一箇中心的事件 bus 並且處理自己的啓動和關閉. 一個 context 定義了一個範圍. 框架 actor 們處在 context 之內,並且在 context 定義的範圍之內進行相互間的通訊. 一個應用程序是可以有多個 context 的. 這對想要加載外部模塊的應用程序很有用. 因爲在一個 context 裏的 actor 只能在他們的 context 定義的範圍之內相互通訊, 所以在一個模塊化的應用程序裏, 不同 context 之間的通訊是完全可能的.

本文檔不討論模塊化編程的內容. 之後本文檔內所有提到的 Context 都指在一個應用程序裏的單一的 context.

Controller & Commands

Controller 層由 Command 類體現. Command 是用來執行應用程序單一單位工作的, 無狀態的, 短生命週期的對象. Command 用於應用程序各層之間相互通訊, 也可能用來發送系統事件. 這些系統事件既可能發動其它的 Command, 也可能被一個 Mediator 接收,然後對一個 View Component 進行對應這個事件的工作. Command 是封裝你的應用程序業務邏輯的絕佳場所.

View & Mediators

View 由 Mediator 類體現. 繼承 Mediator 的類用來處理框架和 View Component 之間的交互. 一個 Mediator 將會監聽框架事件和 View Component 事件, 並在處理所負責的 View Component 發出的事件時發送框架事件. 這樣開發者可以將應用程序特有的邏輯放到 Mediator, 而避免把 View Component 耦合到特定的應用程序.

Model, Service and the Actor

MVCS 架構裏的 service 和 model 在概念上有着非常多的相似之處. 因爲這種相似性, model 和 service 繼承了同樣的 Actor 基類. 繼承 Actor 基類可以獲得很多應用程序架構內的功能. 在 MVCS 的 context 裏, 我們通過利用繼承 Actor 基類來定義應用程序所需要用來管理數據以及和外界通訊的 model 和 service 類. 本文檔將把 model 和 service 類分別叫做 Model 和 Service.

澄清一點, 本文檔把體現應用程序四個層的所有類都稱爲"framework actor"或"actor". 請不要和本例中包含的只被 Model 和 Service 類繼承的 MVCS 類 Actor 混淆.

Model

Model 類用來在 model 層對數據進行封裝併爲其提供 API. Model 會在對數據模型進行某些工作之後發出事件通知. Model 通常具有極高的便攜性.

Service

一個 service 層的 Service 用來和"外面的世界"進行通訊. Web service, 文件存取, 或者其它任何應用程序範圍之外的行爲對 service 類都很適合. Service 類在處理外部事件時會廣播系統事件. 一個 service 應該封裝和外部服務的交互且具有非常高的便攜性.

框架事件

Robotlegs 使用Flash的原生事件用於框架 actor 之間的通訊. 自定義事件類通常用於此用途, 雖然使用現有的 Flash 事件同樣可行. Robotlegs 不支持事件冒泡, 因爲它並不依賴 Flash 顯示列表作爲 event bus. 使用自定義類允許開發者通過給事件添加屬性來爲框架 actor 之間通訊所用的系統事件提供強類型的負載.

所有的框架 actor 都可以發送事件: Mediator, Service, Model, 和 Command. Mediator 是唯一接收框架事件的actor. Command 是在對框架事件的處理中被觸發. 一個事件既可以被一個 Mediator 接收, 也可以觸發一個 command.

model 和 service 不應該監聽和處理事件. 這樣做會把它們緊耦合到應用程序特有邏輯而降低潛在的便攜性和複用性.

Command

Command 是短生命週期的無狀態對象. 它們在被實例化和執行之後立即釋放. Command 應該只在處理框架事件時被執行, 而不應該被任何其他框架 actor 實例化或執行.

Command 職責

Command 被 Context 的 CommandMap 註冊到 Context. CommandMap 在 Context 和 Command 類裏默認可用. Command 類被註冊到 Context 時接收4個參數: 一個事件類型; 響應這個事件時執行的 Command 類; 可選的事件類; 一個是否該 Command 只被執行一次隨即被取消註冊而不響應後續事件觸發的一次性設置.

觸發 Command

Command 被 Mediators, Services, Models, 和其它 Command 廣播的框架事件觸發. 典型的, 觸發這個 Command 的事件會被注入到這個 Command, 以提供對其屬性/負載的訪問:

public class MyCommand extends Command
{
	[Inject]
	public var event:MyCustomEvent;
	
	[Inject]
	public var model:MyModel;
			
	override public function execute():void
	{
		model.updateData( event.myCustomEventPayload )
	}
}

一個被映射的 command 在響應一個框架事件時被實例化, 所有已被映射, 並被 [Inject] 元數據標籤標記過的依賴都會被注入到這個 Command. 另外, 觸發這個 Command 的事件實例也會被注入. 當這些依賴被注入完畢, Command 的執行方法會被自動調用, Command 便會進行它的工作. 你不需要, 而且不應該直接調用 execute() 方法. 這是框架的工作.

鏈接 Command

鏈接 command 也是可行的:

public class MyChainedCommand extends Command
{
	[Inject]
	public var event:MyCustomEvent;
	
	[Inject]
	public var model:MyModel;
			
	override public function execute():void
	{
		model.updateData( event.myCustomEventPayload )
		
		//UPDATED_WITH_NEW_STUFF 觸發一個 command 的同時被
		//一個 mediator 接收然後更新一個View Component, 但是隻在需要這個響應的時候
		if(event.responseNeeded)
		    dispatch( new MyCustomEvent( MyCustomEvent.UPDATED_WITH_NEW_STUFF, model.getCalculatedResponse() ) )
	}
}

使用這種方法可以把需要的任意多的 Command 鏈接在一起. 上面的例子使用了一個條件語句. 如果條件不滿足 Command 就不會被鏈接. 這爲你的 Command 執行應用程序工作提供了極大的靈活性.

應用程序層的解耦

Command 是解耦一個應用程序裏各個 actor 的非常有用的機制. 因爲一個 Command 永遠不會被 Mediator, Model 或者 Service 實例化或執行, 這些類也就不會被耦合到 command, 甚至都不知道 command 的存在.

爲了履行它們的職責, Command 可能:

  • 映射 Mediator, Model, Service, 或者 Context 裏的其它 Command
  • 廣播可能被 Mediator 接收或者觸發其它 Command 的事件.
  • 被注入Model, Service, 和Mediator 以直接進行工作.

需要注意的是, 不建議在一個 Command 裏直接和 Mediator 交互. 雖然這是可行的, 但會將這個 Mediator 耦合到這個 Command. 因爲 Mediator 不像 Service 和 Model, 它可以接受系統事件, 更好的做法是讓 Command 廣播事件, 然後讓需要響應這些事件的 Mediator 監聽它們.

Mediator

Mediator 類用來作爲用戶交互和系統的 View Component 之間的中介. 一個 Mediator 可以在多個級別的粒度上履行它的職責, 中介一個應用程序整體和它的子組件, 或者一個應用程序的任何和所有子組件.

Mediator 職責

Flash, Flex 和 AIR 應用程序爲富視覺用戶界面組件提供了無限的可能. 所有這些平臺都提供了一套組件, 像 DataGrid, Button, Label 和其它常用的UI組件. 也可以繼承這些基本的組件來創建自定義組件, 創建複合組件, 或者完全重寫新的組件.

一個 View Component 是任何的UI組件和/或它的子組件. 一個 View Component 是已被封裝的, 儘可能多地處理自己的狀態和操作. 一個 View Component 提供一個包含了事件, 簡單方法和屬性的API, . Mediators負責代表它所中介的View Component和框架交互. 這包括監聽組件及其子組件的事件, 調用其方法, 和讀取/設置組件的屬性.

一個 Mediator 監聽它的 View Component 的事件, 通過 View Component 暴露的 API 訪問其數據. 一個 Mediators 通過響應其它框架 actor 的事件並對自己的 View Component 進行相應修改來代表它們. 一個 Mediator 通過轉發 View Component 的事件或自己向框架廣播合適的事件來通知其它的框架 actor.

映射一個 Mediator

任何可以訪問到 mediatorMap 實例的類都可以映射 Mediator. 這包括 Mediator, Context, 和 Command 類.

這是映射一個 mediator 的語法:

mediatorMap.mapView( ViewClass, MediatorClass, autoCreate, autoRemove );

View Component 的自動中介

當映射一個 view component 類以獲得中介時, 你可以指定是否自動爲它創建 Mediator. 當此項爲 true 時 context 將監聽這個 view component 的 ADDED_TO_STAGE 事件. 當收到這個事件這個 view component 會被自動中介, 它的 mediator 就可以開始發送和接收框架事件了.

View Component 的手動中介

有時候可能不希望或者不可能使用 view component 的自動中介. 在這種情況下可以手動創建一個 Mediator 類的實例:

mediatorMap.createMediator(contextView);

這裏假設這個 view component 之前已經被 mediatorMapmapView() 方法映射過了.

映射主程序 (contextView) Mediator

映射 contextView 到一個 mediator 是一個常見的模式. 這是個特殊情況, 因爲自動中介對 contextView 不起作用, 因爲它已經被添加到舞臺上, 而不會再發出mediatorMap 自動中介所需要的事件了. 這個映射通常在持有 contextView 引用的 Context 的 setup() 方法裏完成:

override public function startup():void
{
	mediatorMap.mapView(MediateApplicationExample, AppMediator);
	mediatorMap.createMediator(contextView);
}

contextView 並沒有被完全中介, 還可以發送和接受框架事件.

訪問一個 Mediator 的 View Component

當一個 View Component 在一個 Context 的 contextView 裏被添加到舞臺上的時候, 它默認地會被根據 MediatorMap 做映射時的配置被自動中介. 在一個基本的 mediator 裏,viewComponent 會被注入爲被中介的 view component. 一個 Mediator 的 viewComponent 屬性是 Object 類型的. 在大多數情況下, 我們希望訪問一個強類型的對象以從中獲益. 爲此目的, 我們注入被中介的 view component 的強類型實例:

public class GalleryLabelMediator extends Mediator implements IMediator
{
	[Inject]
	public var myCustomComponent:MyCustomComponent;
		
	/**
	* 覆寫 onRegister 是添加此 Mediator 關心的任何系統或 View Component 事件的好機會.
	*/
	override public function onRegister():void
	{
		//添加一個事件監聽器到 Context 來監聽框架事件
		eventMap.mapListener( eventDispatcher, MyCustomEvent.DO_STUFF, handleDoStuff );
		//添加一個事件監聽器到被中介的 view component
		eventMap.mapListener( myCustomComponent, MyCustomEvent.DID_SOME_STUFF, handleDidSomeStuff)
	}
	
	protected function handleDoStuff(event:MyCustomEvent):void
	{
		//把事件的強類型負載設置到 view component 的屬性. 
		//View component 很可能基於這個新數據管理自己的狀態.
		myCustomComponent.aProperty = event.payload
	}
	
	protected function handleDidSomeStuff(event:MyCustomEvent):void
	{
		//把這個事件轉發到框架
		dispatch(event)
	}
}

通過這種方法我們現在可以很方便地訪問被中介的 view component 的公開屬性和方法.

給一個 Mediator 添加事件監聽

事件監聽器是 Mediator 的眼睛和鼻子. 因爲框架內的所有通訊都通過原生的Flash事件, Mediator 可以通過添加事件監聽器來響應感興趣的事件. 除了框架事件, Mediator同時監聽所中介的 view component 的事件.

通常在 Mediator 的 onRegister 方法裏添加事件監聽. 在 Mediator 生命週期中的這個階段, 它已經被註冊並且它的 view component 和其它依賴也都已被注入. 具體的 Mediator 類必須覆寫 onRegister 方法. 也可以在其它方法裏添加事件監聽, 比如響應框架事件和 view component 事件的事件處理方法裏.

Mediators 裝備了一個有 mapListener() 方法的 EventMap. 這個方法註冊每個被添加到 Mediator 的事件監聽, 並且確保 mediator 被框架取消註冊時刪除這些事件監聽. Flash 裏刪除事件監聽是很重要的, 因爲如果一個類裏添加了事件監聽而沒有刪除, Player將無法對此類進行運行時垃圾回收(GC, Garbage Collection). 也可以使用傳統的 Flash 語法添加事件監聽器, 但要注意也要手動把它們刪除.

監聽框架事件

所有框架裏的actor在實例化時都會被注入一個 eventDispatcher 屬性. 這個 eventDispatcher 就是 Mediator 發送和接受框架事件的機制.

eventMap.mapListener(eventDispatcher, SomeEvent.IT_IS_IMPORTANT, handleFrameworkEvent)

通過此語法, 一個 Mediator 現在監聽了 SomeEvent.IT_IS_IMPORTANT 事件並在 handleFrameworkEvent 方法裏處理它.

廣播框架事件

Mediator的一個很重要的職責就是向框架發送其它 actor 可能感興趣的事件. 這些事件通常是在響應應用程序用戶和被中介的 view component 之間的一些交互時發出的. 這裏同樣有一個可以減少發送事件到框架的代碼輸入的很有用的方法:

dispatch(new SomeEvent(SomeEvent.YOU_WILL_WANT_THIS, myViewComponent.someData))

這個事件現在可以被其它 Mediator 接收或者執行一個 command 了. 發出事件的 Mediator 並不關心其它的 actor 如何迴應這個事件, 它只是簡單地廣播一條有事發生的消息. 一個 mediator 也可以監聽自己發出的事件, 然後據此作出迴應.

監聽 View Component 事件

Mediator 負責所中介的 view component 發出的事件. 這可以是個獨立組件, 比如 TextField 或者 Button, 也可以是有嵌套層級的複雜組件. 當 mediator 收到 view component 發出的事件會使用指定的方法處理它. 和框架事件一樣, EventMap 的 mapListener 方法是給一個 mediator 添加事件監聽的首選.

eventMap.mapListener(myMediatedViewComponent, SomeEvent.USER_DID_SOMETHING, handleUserDidSomethingEvent)

響應一個 view component 的事件時, 一個 mediator 可能:

  • 考察事件的負載 (如果有)
  • 考察 view component 的當前狀態
  • 對 view component 進行需要的工作
  • 發送系統事件以通知其它actor有事發生

通過 Mediator 訪問 Model 和 Service

你的 mediator 可以監聽 Service 和 Model 類派出的系統事件來提高松耦合性. 通過監聽事件, 你的 mediator 不需要關心事件來源, 而只需直接使用事件攜帶的強類型的負載. 因此, 多個 mediator 可以監聽相同的事件然後根據所收到的數據調整自己的狀態.

在一個 mediator 裏直接訪問 service 可以提供很大便利而不會帶來嚴重的耦合性問題. 一個 service 並不存儲數據, 只是簡單地提供一個向外部service發送請求並接受響應的API. 能夠直接訪問這個API可以避免在你的應用程序中增加不需要的 command 類來達到同樣目的. 如果這個 service API 在很多 mediator 中通過相同的方式訪問, 將此行爲封裝到一個 command 裏有益於保持此行爲的一致性並減少對此 service 的反覆調用以及在你的 mediator 裏的直接訪問.

建議通過 model 和 service 實現的接口將 model 和 service 注入 mediator. 下面的 Service 實例 章節可以找到一個這樣的例子.

訪問其它 Mediator

如同 Service 和 Model,在一個 Mediator 裏也可以注入和訪問其它的 Mediator. 這種做法是 強烈不建議的 因爲這種緊耦合可以簡單地通過使用框架事件進行通訊而避免.

Model

Model 類用來管理對應用程序的數據模型的訪問. Model 爲其它框架actor提供一個 API 來訪問, 操作和更新應用程序數據. 這個數據包括但不限於原生數據類型比如 String, Array, 或者像 ArrayCollection 一樣的域特有對象或集合.

Model 有時被當做簡單的 Model 比如 UserModel, 有時也被當做 Proxy 比如 UserProxy. 在 Robotlegs 裏, 這兩種命名都是用作相同的目的, 爲應用程序數據提供一個 API. 不管採用哪種命名 model 都繼承提供了核心框架依賴和一些有用方法的 Actor 基類. 本文檔將這些類當做 Model.

Model 職責

Model類封裝了應用程序數據模型併爲其提供一個 API. 一個 Model 類是你的應用程序數據的看門人. 應用程序裏的其它 actor 通過 Model 提供的 API 請求數據. 因爲數據是通過 Model 更新, Model 裝備了向框架廣播事件的機制以向其它 actor 通知數據模型的變化使它們得以據此調整自己的狀態.

除了控制對數據模型的訪問, Model 通常也被用來保證數據狀態的有效性. 這包括對數據進行計算, 或域特有邏輯的其它領域. Model 的這個職責非常重要. Model 是應用程序中最有潛力具有便攜性的層. 通過把域邏輯放入 Model, 以後的 model 實現就不再需要像把域邏輯放入 View 或 Controller 層那樣重複這些相同的邏輯,

作爲一個例子, 你的 Model 裏可能執行購物車數據的計算. 一個 Command 將會訪問這個方法, 最終的計算結果將會被作爲被某個 Mediator 監聽的事件派發出去. 這個 mediator 將會根據這個被更新的數據更新自己的 view component, 應用程序的第一個迭代是個典型的 Flex 程序. 這個計算也很容易在一個 Mediator 甚至視圖裏進行. 應用程序的第二個迭代是一個需要全新視圖元素的移動設備 Flash 應用程序. 因爲這個邏輯在 Model 裏, 所以可以很容易被兩個完全不同元素的視圖複用.

映射一個 Model

Injector 有幾個方法可以用來將你的 Model 類映射到你的框架actor. 另外, 這些方法事實上可以用來注入任何類到你的類裏.

將一個已存在的實例當做一個單例注入映射, 使用下面的語法:

injector.mapValue(MyModelClass, myModelClassInstance)

爲每個注入映射一個類的新實例, 使用下面的語法:

injector.mapClass(MyModelClass, MyModelClass)

另外, 這也可以用來使用被注入的實現某接口的合適的類來映射這個用來注入的接口.
injector.mapClass(IMyModelClass, MyModelClass)

爲某個接口或類映射一個單例實例, 使用下面的語法:

injector.mapSingleton(MyModelClass, MyModelClass)

需要注意重要的一點, 當提及上面的一個單例時, 它並不是一個單例模式的單例. 在這個 Context 之外並不強制它作爲一個單例. Injector 簡單地確保這個類的唯一一個實例被注入. 這對處理你的應用程序數據模型的 Model 非常重要.

從一個Model裏廣播事件

Model 類提供一個方便的 dispatch 方法用來發送框架事件:

dispatch( new ImportantDataEvent(ImportantDataEvent.IMPORTANT_DATA_UPDATED))

有很多理由派發一個事件, 包括但不限於:

  • 數據已被初始化並準備好被其它 actor 使用
  • 一些數據片被添加到 Model
  • 數據被從 Model 中刪除
  • 數據已改變或者更新
  • 數據相關的狀態已改變

在一個 Model 裏監聽框架事件

雖然技術上可能, 但 強烈不建議 這樣做. 不要這樣做. 只是爲了說清楚: 不要這樣做. 如果你這樣做了, 不要說你沒被警告過.

Service

Service 用來訪問應用程序範圍之外的資源. 這包括但當然不限於:

  • web services
  • 文件系統
  • 數據庫
  • RESTful APIs
  • 通過 localConnection 的其它 Flash 應用程序

Service 封裝了這些和外部實體的交互, 並管理這個交互產生的 result , fault 或其它事件.

你可能注意到 Service 和 Model 的基類非常相像. 事實上, 你可能注意到除了類名, 它們其實是一樣的. 那麼爲什麼用兩個類呢? Model 和 Service 類在一個應用程序裏有完全不同的職責. 這些類的具體實現將不再相像. 如果沒有這個分離, 你將經常發現 Model 類在訪問外部服務. 這讓 Model 有很多職責, 訪問外部數據, 解析結果, 處理失敗, 管理應用程序數據狀態, 爲數據提供一個 API, 爲外部服務提供一個 API, 等等. 通過分離這些層有助於緩解這個問題.

Service 職責

一個 Service 類爲你的應用程序提供一個和外部服務交互的 API. 一個 service 類將連接外部服務並管理它收到的響應. Service 類通常是無狀態的實體. 他們並不存儲從外部服務收到的數據, 而是發送框架事件來讓合適的框架 actor 管理響應數據和失敗.

映射一個 Service

injector 的多個可用的方法 可以用來映射你的 Service 類以注入你的其它框架 actor. 另外, 這些方法也可以用來注入事實上任何類到你的類裏.

將一個已存在的實例當做一個單例注入映射, 使用下面的語法:

injector.mapValue(MyServiceClass, myServiceClassInstance)

爲每個注入映射一個類的新實例, 使用下面的語法:

injector.mapClass(MyServiceClass, MyServiceClass)

另外, 這也可以用來使用被注入的實現某接口的合適的類來映射這個用來注入的接口.
injector.mapClass(IMyServiceClass, MyServiceClass)

爲某個接口或類映射一個單例實例, 使用下面的語法:

injector.mapSingleton(MyServiceClass, MyServiceClass)

需要注意重要的一點, 當提及上面的一個單例時, 它並不是一個單例模式的單例. 在這個 Context 之外並不強制它作爲一個單例. Injector 簡單地確保這個類的唯一一個實例被注入.

在一個 Service 裏監聽框架事件

雖然技術上可能, 但 強烈不建議 這樣做. 不要這樣做. 只是爲了說清楚: 不要這樣做. 如果你這樣做了, 不要說你沒被警告過.

廣播框架事件

Service 類提供一個方便的 dispatch 方法用來發送框架事件:

dispatch( new ImportantServiceEvent(ImportantServiceEvent.IMPORTANT_SERVICE_EVENT))

Service 示例

下面是來自 Image Gallery demo 的 Flickr service 類 . The Flickr API AS3 Library 做了很多連接到 Flickr 的底層處理. 這個例子使用了它併爲在這個例子範圍內使用提供了一個簡單的抽象.

package org.robotlegs.demos.imagegallery.remote.services
{
	import com.adobe.webapis.flickr.FlickrService;
	import com.adobe.webapis.flickr.Photo;
	import com.adobe.webapis.flickr.events.FlickrResultEvent;
	import com.adobe.webapis.flickr.methodgroups.Photos;
	import com.adobe.webapis.flickr.methodgroups.helpers.PhotoSearchParams;
	
	import org.robotlegs.demos.imagegallery.events.GalleryEvent;
	import org.robotlegs.demos.imagegallery.models.vo.Gallery;
	import org.robotlegs.demos.imagegallery.models.vo.GalleryImage;
	import org.robotlegs.mvcs.Actor;

    /**
     *  這個類使用了 Adobe 提供的 Flickr API 來連接到 
     *  Flickr 並獲取圖片. 它最開始加載當前最"有趣"的
     *  的照片, 同時也提供了搜索其它關鍵詞的能力. 
     */
	public class FlickrImageService extends Actor implements IGalleryImageService
	{
		private var service:FlickrService;
		private var photos:Photos;
		
		protected static const FLICKR_API_KEY:String = "516ab798392cb79523691e6dd79005c2";
		protected static const FLICKR_SECRET:String = "8f7e19a3ae7a25c9";
		
		public function FlickrImageService()
		{
			this.service = new FlickrService(FLICKR_API_KEY);
		}
		
		public function get searchAvailable():Boolean
		{
			return true;
		}
				
		public function loadGallery():void
		{
			service.addEventListener(FlickrResultEvent.INTERESTINGNESS_GET_LIST, handleSearchResult);
			service.interestingness.getList(null,"",20)
		}
		
		public function search(searchTerm:String):void
		{
			if(!this.photos)
				this.photos = new Photos(this.service);
			service.addEventListener(FlickrResultEvent.PHOTOS_SEARCH, handleSearchResult);
			var p:PhotoSearchParams = new PhotoSearchParams()
			p.text = searchTerm;
			p.per_page = 20;
			p.content_type = 1;
			p.media = "photo"
			p.sort = "date-posted-desc";
			this.photos.searchWithParamHelper(p);				
		}
		
		protected function handleSearchResult(event:FlickrResultEvent):void
		{
			this.processFlickrPhotoResults(event.data.photos.photos);
		}
		
		protected function processFlickrPhotoResults(results:Array):void
		{
			var gallery:Gallery = new Gallery();
						
			for each(var flickrPhoto:Photo in results)
			{
				var photo:GalleryImage = new GalleryImage()
				var baseURL:String = 'http://farm' + flickrPhoto.farmId + '.static.flickr.com/' + flickrPhoto.server + '/' + flickrPhoto.id + '_' + flickrPhoto.secret;
				photo.thumbURL = baseURL + '_s.jpg';
				photo.URL = baseURL + '.jpg';
				gallery.photos.addItem( photo );
			}
			
			dispatch(new GalleryEvent(GalleryEvent.GALLERY_LOADED, gallery));
		}
		
	}
}

FlickrGalleryService 提供了一個連接到一個 gallery 服務的非常簡單的接口. 應用程序可以 loadGallery,search, 並查詢 searchAvailable 是 true 還是 false. IGalleryService 接口定義的接口:

package org.robotlegs.demos.imagegallery.remote.services
{
	public interface IGalleryImageService
	{
		function loadGallery():void;
		function search(searchTerm:String):void;
		function get searchAvailable():Boolean;
	}
}

Services 應該實現一個接口

通過創建實現了接口的 service, 爲了測試在運行時切換它們, 或者對應用程序的最終用戶提供對其它 service 的訪問將會很簡單. 比如, FlickrGalleryService 可以很容易替換爲 XMLGalleryService:

package org.robotlegs.demos.imagegallery.remote.services
{
	import mx.rpc.AsyncToken;
	import mx.rpc.Responder;
	import mx.rpc.http.HTTPService;
	
	import org.robotlegs.demos.imagegallery.events.GalleryEvent;
	import org.robotlegs.demos.imagegallery.models.vo.Gallery;
	import org.robotlegs.demos.imagegallery.models.vo.GalleryImage;
	import org.robotlegs.mvcs.Actor;

	public class XMLImageService extends Actor implements IGalleryImageService
	{
		protected static const BASE_URL:String = "assets/gallery/";
		
		public function XMLImageService()
		{
			super();
		}

		public function get searchAvailable():Boolean
		{
			return false;
		}
				
		public function loadGallery():void
		{
			var service:HTTPService = new HTTPService();
			var responder:Responder = new Responder(handleServiceResult, handleServiceFault);
			var token:AsyncToken;
			service.resultFormat = "e4x";
			service.url = BASE_URL+"gallery.xml";
			token = service.send();
			token.addResponder(responder);
		}
		
		public function search(searchTerm:String):void
		{
			trace("search is not available");
		}
		
		protected function handleServiceResult(event:Object):void
		{
			var gallery:Gallery = new Gallery();
						
			for each(var image:XML in event.result.image)
			{
				var photo:GalleryImage = new GalleryImage()
				photo.thumbURL = BASE_URL + "images/" + image.@name + '_s.jpg';
				photo.URL = BASE_URL + "images/" + image.@name + '.jpg';
				gallery.photos.addItem( photo );
			}
			
			dispatchEvent(new GalleryEvent(GalleryEvent.GALLERY_LOADED, gallery));
		}
		
		protected function handleServiceFault(event:Object):void
		{
			trace(event);
		}
	}
}

XML gallery 提供了和 Flickr 一樣的方法可以訪問並可在任何 IGalleryService 接口被調用的地方進行替換. 這些 service 派發相同的事件並且在最終的應用程序裏很難區分. 在這個例子裏, 搜索並沒有被實現, 但搜索功能在這個 service 裏也同樣可以很容易實現,

建議所有的 service 都實現一個定義了它們 API 的接口. 在框架 actor 裏接收一個 service 作爲依賴注入時可以請求這個接口, 而不是具體的實現.

injector.mapSingletonOf(IGalleryService, FlickrGalleryService);
[Inject]
public var galleryService:IGalleryService

你可以通過簡單地改變注入來使用你的類代替這個 gallery service:

injector.mapSingletonOf(IGalleryService, XMLGalleryService);

這種方式可以爲一個應用程序提供健壯性, 靈活性, 和增強的可測試性.

在一個 Service 裏解析數據

在上面的例子裏 service 類或者外部服務提供了不符合應用程序域的對象. Flickr service 提供強類型的 Photo 對象而 XML service 提供 xml. 這些數據類型都很好用, 但是並不符合我們應用程序的 context. 它們是外來者. 可以圍繞外部數據類型對應用程序進行建模, 或者更可取地, 轉換這些數據以符合應用程序.

應用程序裏有兩處可以進行這項操作/轉換. Service 和 Model 都很適合. Service 是進入外部數據的第一個點, 所以它是操作一個外部服務返回的數據的更好的選擇. 外來數據應該在第一個機會轉換到應用程序域.

提供一個使用工廠類而不是在 service 裏生成應用程序域對象的例子… 適當的

當數據被轉換爲應用程序域特有的對象之後發出帶有強類型負載的事件以被對此關心的 actor 立即使用.

Service 事件

service 組合的最後一個部分是自定義事件. 沒有事件的 service 只是啞巴. 他們可能做的任何工作都不會被其它框架成員注意到. 一個 service 將會使用自定義事件來嚮應用程序發出聲音. 事件並不一定是唯一的意圖. 如果這個 service 正在轉換數據它可以使用一個普通的事件來派發強類型的數據給感興趣的應用程序 actor.


(原文地址:https://github.com/robotlegs/robotlegs-documentation/blob/master/best-practices-zh-cn.textile

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章