Parsley 開發指南 7
7 管理命令
Parsley 3中的命令支援完全重寫了在Parsley 2中的DynamicCommand 設施。你實現一個簡單的命令的方式基本上仍然是相同的,你仍然可以像在以前的版本中把命令對映到訊息。但是實現已經完全變了,還添加了很多新功能。
Parsley 3的命令支援是在獨立的Spicelib Commands庫上構建的。庫已經有方便的方式來實現一個命令,將它們分組為順序或並行執行,或基於個人的命令結果用decision points建立動態flows。它還允許在序列中從一個命令傳遞結果到後續命令。因此,建議首先開始閱讀文件 21命令框架。本章將只提供覆蓋在Spicelib手冊中簡短摘要的內容,轉而關注Parsley在庫上增加的特性。
Parsley在Spicelib Commands之上提供的特性是:
l 只在它執行的時候動態地新增一個命令到上下文中。這樣一個命令執行的時候可以被注入,傳送和接收訊息,利用任何內建或自定義容器特性。而在執行完成時會從上下文中得到自動處理和移除。詳情檢視 7.7命令生命週期.
l 除了Spicelib提供的Flow API來構建,鏈和執行命令,Parsley在MXML或XML中提供了一種宣告的方式來配置命令(or sequences or flows) 。詳情檢視 7.3命令組 和7.4命令流.
l 像 Parsley 2中的DynamicCommands,它允許你對映一個命令(或序列或流)到一個訊息,因此無論什麼時候一個匹配訊息被接受到,容器自動例項化和執行一個新的命令。詳情檢視
如果你不需要上面所提到的任何特性,你可以使用Spicelib Commands的單獨模式。如果命令不需要注入,訊息傳遞或其他容器功能,執行命令或命令序列在非託管模式是一個完全有效的用例。一組命令處理一個單獨的、孤立的任務,這是一個相當普遍的用例。請記住,獨立的命令專案已經提供了大量的容器般的便利功能,比如能夠透明地從命令傳遞資料到後續的命令。你應該總是先從最輕量的選擇,如果你稍後發現,你需要Parsley的特性命令,你通常通過新增幾行程式碼就可以輕鬆地“升級”,同時保留一切不變。
7.1實現一個命令
關於如何實現一個命令的一個稍微詳細文件可以在21.1實現一個命令 可以找到,在Spicelib手冊的部分。本章將提供一個快速概述與幾個例子和描述當命令被Parsley管理的時候你有的額外選項。
7.1.1 同步命令
public class SimpleCommand {
public function execute (): void {
trace("I execute, Therefore I am");
}
}
該方法執行是由命名約定,沒有介面來實現或基類來擴充套件。這允許一些靈活的execute方法的簽名,這可能會收到結果回撥函式,之前的命令或訊息的結果,引發了這個命令。
7.1.2 用結果的同步命令
public class SimpleCommand {
public function execute (): String {
return "This is the result";
}
}
一個同步的執行方法可能宣告一個不同的返回型別的情況比返回void更多。在這種情況下,將返回值解釋為該命令的結果。結果可能會傳遞給方法的執行後續命令(在一個序列或流)或解耦的結果處理程式。詳細資訊請參閱 7.6處理結果和觀察命令。
7.1.3用結果的非同步命令
public class GetUserListCommand {
[Inject("myService")]
public var service: RemoteObject;
public var callback: Function;
public function execute (): void {
service.getUserList().addResponder(new Responder(result, error));
}
private function result (result: ResultEvent): void {
callback(result.result);
}
private function error (event: FaultEvent): void {
callback(event.fault);
}
}
一個命令被框架是看做非同步的,因為它要麼就有一個public var的Function型別的callback ,要麼execute 方法中接受一個Function型別的方法引數,在這些情況下,框架將注入一個函式,可用於標記命令完成或錯誤。對於常規的結果和錯誤,最後僅僅是傳遞給回撥函式,框架可以基於物件的型別區分結果和錯誤。
7.1.4 用非同步令牌的非同步命令
public class GetUserListCommand {
[Inject("myService")]
public var service: RemoteObject;
public function execute (): AsyncToken {
return service.getUserList();
}
}
當你所有要做的就是呼叫RemoteObject,並把結果或錯誤返回到框架中進行進一步的加工的話,那麼有一個快捷方式就像上面所示。它代替你做關於Responder的事,你把這個任務留給了框架。從技術上講,上面的命令生成一個同步的命令,但是那是框架知道的一個型別(在本例中AsyncToken),不是我們立即可用的。框架將等待result或fault 並且把它當做命令的結果。對AsyncToken 的支援構建到了Parsley中,但是你可以為其他型別建立自定義的 21.5.2實現結果處理器,如果需要的話。
7.1.5 命令中的結果處理器
public class GetUserListCommand {
[Inject("myService")]
public var service: RemoteObject;
public function execute (): AsyncToken {
return service.getUserList();
}
public function result (users: Array): void {
// process users
}
}
如果你使用AsyncTokens的快捷方式,你可能仍然需要在傳遞給應用程式的其他部分之前就在命令中處理結果。結果處理器的方法名必須是result,錯誤處理器的方法名必須是error。它必須接受一個引數,引數型別可以是你遠端呼叫的返回型別。
7.1.6 改變結果
public class GetUserProfileCommand {
[Inject("myService")]
public var service: RemoteObject;
public function execute (msg: GetUserProfileMessage): AsyncToken {
return service.getUserProfile(msg.userId);
}
public function result (profile: XML): UserProfile {
return parse(profile);
}
}
你也可以覆蓋result 如果你想修改或在它被傳遞到應用程式的其他部分之前改變它。在該示例中,你從伺服器接收XML,但是在解析XML之後返回UserProfile例項的結果。UserProfile將被視為命令的結果。
7.1.7 其他選項
因為管理命令是在SpicelibCommands基礎上構建的,當在Parsley中使用命令的時候,所有該庫的特性都可以使用。更多的例子可以在Spicelib手冊中找到:
l 一個命令,執行同步或非同步取決於一個匹配的結果是否已經快取在客戶端的例子在 21.1.2 非同步命令.
l 一個命令可以取消,在外面和在命令裡面都是,檢視 21.1.4命令取消。
l 更多選項關於如何產生一個錯誤的結果,檢視 21.1.3錯誤處理。
7.2 對映訊息到命令
如果你在Parsley 2中使用過DynamicCommands,你已經很熟悉對映命令訊息的概念。在Parsley 3中這個特性已經被泛化,允許對映任何命令,包括序列和流到Parsley 訊息中。只要他的實現可以通過新的Spicelib Commands library執行(有些特性只能在Parsley中使用)
7.2.1 在MXML中對映命令
一個對映宣告可以很簡單,比如這個:
<parsley:MapCommand type="{LoadUserCommand}"/>
這是唯一的能夠讓指定型別的命令來執行匹配的訊息。命令需要對映的訊息的型別是由execute方法的方法簽名來決定的。它可以接收觸發器訊息作為方法引數:
public class GetUserProfileCommand {
[Inject("myService")]
public var service: RemoteObject;
public function execute (msg: GetUserProfileMessage): AsyncToken {
return service.getUserProfile(msg.userId);
}
}
跟往常一樣當有東西被按照型別配置在Parsley中的時候,這是多型地解譯,因此命令還將執行GetUserProfileMessage的子類。
對於每一個匹配的訊息,容器將建立一個新的命令的例項,將它新增到上下文,以便它可以作為一個完全託管物件,例如注入,傳送訊息等等,處理命令的結果,例如傳遞它到其他物件中被配置為結果處理器對應的方法中,最後在執行後把它從上下文中移除。
Explicit Message Type Configuration
如果你不想把訊息傳給execute方法(例如,以避免不必要的依賴項),還可以顯式地在MXML中指定訊息型別:
<parsley: MapCommand type="{LoadUserCommand}" messageType="{LoadUserMessage}"/>
Full Command Configuration
如果你想指定的不僅僅是命令的型別,比如設定屬性值或建構函式引數,像配置在MXML中的常規物件一樣,你可以使用一個巢狀的命令標籤:
<parsley:MapCommand>
<parsley:Command type="{LoadUserProfileCommand}">
<parsley:Property name="type" value="{UserProfile.EXTENDED}"/>
<parsley:Property name="cache" value="true"/>
</parsley:Command>
</parsley:MapCommand>
Other Options
標籤還支援[MessageHandler]的大多數屬性:
<parsley:MapCommand
type="{ShoppingCartCommand}"
selector="save"
scope="local"
order="1"
/>
7.2.2 在MXML中對映命令
在XML中對映命令的語法跟在MXML是一樣的,只是使用破折號代替駝峰式大小寫:
<map-command
type="com.company.shop.ShoppingCartCommand"
selector="save"
scope="local"
order="1"
/>
7.2.3 程式設計方式對映命令
最後你可以程式設計來對映一個命令:
var context: Context = ...
MappedCommands
.create(GetUserProfileCommand)
.scope(ScopeName.LOCAL)
.register(context);
在上述例子中,上下文會建立一個GetUserProfileCommand的新例項,每當一個匹配的訊息(execute 方法的簽名)在local scope 被聽到的時候。
如果你需要比僅指定類更多的設定,你需要傳遞一個工廠函式。你不能傳遞現有的命令例項,容器必須為每個訊息建立一個新的例項:
private function createCommand (): Object {
var com:GetUserProfileCommand = new GetUserProfileCommand(someParam);
com.profileType = ProfileType.EXTENDED;
return com;
}
[...]
var context: Context = ...
MappedCommands
.factoryFunction(createCommand, GetUserProfileCommand)
.scope(ScopeName.LOCAL)
.register(context);
7.3命令組
Spicelib Commands庫提供了選項來配置一組命令按照序列或並行執行。如果你想要以程式設計方式配置這樣一個組,並且在非託管模式下執行它,你可以在Spicelib手冊中閱讀 21.2命令組。這一章討論Parsley新增的選項。
7.3.1 Declaring Groups in MXML
對映一系列的命令到訊息,你可以使用以下語法:
<parsley:MapCommand messageType="{LoginMessage}">
<parsley:CommandSequence>
<parsley:Command type="{LoginCommand}"/>
<parsley:Command type="{LoadUserProfileCommand}"/>
</parsley:CommandSequence>
</parsley:MapCommand>
在這一章的大多數示例會展示如何對映一組命令到訊息。此外,你還可以宣告命令組作為工廠(包在<CommandFactory>標籤裡),然後以程式設計方式執行它們。但是你不能在MXML配置類的最外層新增一個<CommandSequence>標籤。
對於並行執行你只需要用<ParallelCommands>標籤替換 <CommandSequence>:
<parsley:MapCommand messageType="{LoadDashboardMessage}">
<parsley:ParallelCommands>
<parsley:Command type="{LoadUserProfileCommand}"/>
<parsley:Command type="{LoadPrivateMailboxCommand}"/>
</parsley:ParallelCommands>
</parsley:MapCommand>
7.3.2 Declaring Groups in XML
跟之前一樣語法是相同的,你只需要切換破折號:
<map-command message-type="com.bluebeard.auth.LoginMessage">
<command-sequence>
<command type="com.bluebeard.auth.LoginCommand"/>
<command type="com.bluebeard.user.LoadUserProfileCommand"/>
</command-sequence>
</map-command>
7.3.3 Using Results from Preceding Commands
結果可以用解耦方式傳遞到序列中的後續命令中。如果你的LoginCommand處理一個User的例項,下一個命令可以在execute方法中接受它作為一個引數,連同其他引數,如回撥函式或引發了序列的訊息:
public class GetUserProfileCommand {
public function execute (user: User, callback: Function): void {
[...]
}
}
引數的順序無關緊要。如果前面有多個命令,都產生了一個結果,那麼只會按型別匹配。如果不止一個命令產生了相同的型別,那麼最後一個將被注入。
7.4命令流
命令流添加了決策點的概念,來定義一個動態的命令序列,下一個命令的執行是由檢查前一個命令的結果後的連結(link)決定的。
Parsley提供了MXML和XML標籤來定義一個流(flow),包括所有預設的Spicelib Commands 庫提供的連結型別。
<parsley:MapCommand messageType="{LoginMessage}">
<parsley:CommandFlow>
<parsley:Command type="{LoginCommand}">
<parsley:LinkResultType type="{AdminUser}" to="{loadAdminConsole}"/>
<parsley:LinkResultType type="{User}" to="{loadProfile}"/>
</parsley:Command>
<parsley:Command id="loadAdminConsole" type="{LoadAdminConsoleCommand}">
<parsley:LinkAllResults to="{loadProfile}"/>
</parsley:Command>
<parsley:Command id="loadProfile" type="{LoadProfileCommand}"/>
</parsley:CommandFlow>
</parsley:MapCommand>
在上述例子中,LoadAdminConsoleCommand只會在LoginCommand產生一個AdminUser型別的結果的時候才會執行。
對映一個流到訊息中跟序列一樣是可用的,所有我們不會在這裡重複它們。
Parsley提供了所有Spicelib Commands提供的內建連結(link)型別的XML和MXML標籤。
Linking by Result Type
<parsley:Command type="{LoginCommand}">
<parsley:LinkResultType type="{AdminUser}" to="{loadAdminConsole}"/>
<parsley:LinkResultType type="{User}" to="{loadProfile}"/>
</parsley:Command>
Linking by Result Value
<parsley:Command type="{LoginCommand}">
<parsley:LinkResultValue value="{Login.ADMIN}" to="{loadAdminConsole}"/>
<parsley:LinkResultValue value="{Login.USER}" to="{loadProfile}"/>
</parsley:Command>
Linking by Property Value
<parsley:Command type="{LoginCommand}">
<parsley:LinkResultProperty name="isAdmin" value="{true}" to="{loadAdminConsole}"/>
<parsley:LinkResultProperty name="isAdmin" value="{false}" to="{loadProfile}"/>
</parsley:Command>
Linking all Results of a Command
<parsley:Command type="{LoginCommand}">
<parsley:LinkAllResults to="{loadAdminConsole}"/>
</parsley:Command>
Custom Links
一個自定義連結類封裝可以新增更復雜的邏輯。任何實現了LinkTag介面都可以作為<Command>的子標籤。
<Command type="{LoginCommand}">
<links:MyCustomLinkType/>
</Command>
7.5 通過程式設計的方式開始一個命令
當你不對映一個命令到訊息你仍然可以以程式設計方式觸發命令執行。要做到這一點,你基本上有兩種選擇:
l 在MXML或者XML中配置命令,注入一個CommandFactory到任意託管物件中,,然後使用工廠建立命令例項並且執行它們。
l 以程式設計方式建立命令(使用常規Spicelib API),然後傳遞命令到Parsley託管執行。
本節對於兩種情況都給出了示例。
7.5.1 注入命令工廠
首先,你需要在MXML或XML中宣告一個命令工廠。工廠可以產生一個單獨的命令,一個序列或流:
<parsley:CommandFactory id="loginCommand">
<parsley:CommandSequence>
<parsley:Command type="{LoginCommand}"/>
<parsley:Command type="{LoadUserProfileCommand}"/>
</parsley:CommandSequence>
</parsley:CommandFactory>
然後你可以在同一個上下文或子上下文中將這個工廠注入到任何託管物件。
[Inject("loginCommand")]
public var loginCommand: ManagedCommandFactory;
型別必須是ManagedCommandFactory,id是可選的如果你只宣告一個單獨的工廠在每個上下文中。
然後你可以用這個工廠建立任意數量的新命令例項並且執行它們:
loginCommand.newInstance().execute();
這個命令會在它執行的時候被新增到上下文中,就像其他有Parsley提供的命令變體一樣。
7.5.2 手動啟動託管執行
這個選項允許你用Spicelib API配置命令然後傳遞給Parsley託管執行。
var loginSequence: Command = Commands
.asSequence()
.add(new LoginCommand())
.add(new LoadUserProfileCommand())
.complete(completeHandler)
.error(errorHandler)
.build();
var context: Context = ...;
ManagedCommands
.wrap(loginSequence)
.execute(context);
序列的設定是用的Spicelib API,但是代替了就像你使用Spicelib Commands一樣直接呼叫execute,你呼叫build 然後傳遞這個配置好的命令給Parsley託管執行。
如果你只需要指定的命令型別是不需要進一步的配置的單個命令,你可以選擇跳過Spicelib設定步驟:
var context:Context = ...;
ManagedCommands
.create(MyCommand)
.execute(context);
7.6處理結果和觀察命令
Parsley提供多種方式處理結果或觀察命令執行。他們將在以下部分中描述。
7.6.1 命令結果處理器
當你使用一個execute方法返回AsyncToken 的命令的時候,你可以在命令內部使用結果處理器 7.1.5 命令結果處理器。這些內部結果處理程式將總是在其他物件中的解耦處理器之前呼叫。它們還可能在外部結果處理程式得到呼叫之前修改結果。
7.6.2 解耦的結果處理器和命令觀察者
除了命令本身的結果處理器,其他的物件也可以得到通知。有一組標籤在託管物件中宣告這些(像大多數標籤一樣有元資料,MXML和XML標籤)。
CommandResult
這個標籤可以用來獲得其他的物件的命令產生的結果(這不需要也不應該被用來在命令內部定義一個結果處理程式)。
[CommandResult]
public function handleResult (user:User, message:LoginMessage) : void {
在本例中,遠端呼叫返回的User例項會和觸發這個動作的原始訊息一起被傳遞到結果處理器。像正常的訊息處理器一樣,訊息的引數型別是用於確定哪些處理程式來呼叫。它總是一個組合的訊息型別(多型)和一個可選的選擇器值,作為二次選擇的鍵。結果的型別還必須匹配這個方法來得到呼叫。
如果產生User 例項的命令是序列的一部分,是由特定的訊息型別觸發的,那麼在預設情況下結果處理程式只會在整個序列已經完成之後呼叫。
如果你需要儘早處理結果,你可以使用immediate屬性:
[CommandResult(immediate="true")]
public function handleResult (user:User, message:LoginMessage) : void {
如果命令不屬於一個序列或流,這個屬性沒有任何影響。
CommandComplete
如果你對實際結果不感興趣,而是隻想在命令完成後執行某些邏輯,你可以使用另一個標籤:
[CommandComplete]
public function userSaved (message:SaveUserMessage) : void {
這意味著每當SaveUserMessage觸發的命令完成的時候這個方法都會得到呼叫。如果是序列這意味著方法會在序列中的所有命令成功完成的時候呼叫。
CommandError
This tag can be used for receiving the eventual fault events or other errors.
[CommandError]
public function handleResult (fault:FaultEvent, trigger:SaveUserMessage) : void {
引數又都是可選的,並且匹配規則跟[CommandResult]是一樣的.’
Overriding the Result
像一個命令裡的結果處理器,解耦的結果處理程式也可以覆蓋原有的結果。要這樣做方法需要一個額外的CommandObserverProcessor型別的引數,所以實際的技術跟內部結果處理程式有一點不同。
[CommandResult]
public function handleResult
(result:XML, msg:LoginMessage, processor:CommandObserverProcessor) : void {
var parsedResult:Object = parseResultSomehow(result);
processor.changeResult(parsedResult);
}
CommandObserverProcessor介面是MessageProcessor的子介面,它提供訪問在共同的MessageProcessor功能上執行的Command 。
Local Result Handlers
Parsley有在全域性作用域中執行的命令的local結果處理器。這解決了在模組化應用程式一個常見的問題,一個選項卡或視窗發出一個資訊,需要在根應用程式的共享服務中觸發一個命令。共享服務中的命令必須監聽全域性作用域,因為它不屬於載入到那個標籤或視窗的模組中傳送物件的上下文。但對於結果處理,經常只有這個特殊的標籤或視窗想要處理它。因為這個原因,命令的結果和錯誤總是會重新從引發命令的訊息的起源處路由到上下文。
這允許使用一個本地處理程式如下:
[CommandResult(scope="local")]
public function handleResult (result:Object, msg:LoginMessage) : void {
[...]
}
這甚至當命令在父上下文中執行的時候也起作用,只要觸發訊息的源跟這個處理器是同一個上下文。除了這種選擇,當應用程式的任何部分監聽全域性作用域的時候,仍然能夠處理結果。
7.6.3命令狀態標誌
最後你也可以觀察命令執行的狀態:
[CommandStatus(type="com.foo.messages.SaveUserMessage")]
public var isSaving:Boolean;
如果匹配指定型別和選擇器的一個或多個非同步命令正在執行,這個布林標誌將永遠是true。否則將是false。這對任務來說非常方便,比如在命令執行的時候禁用按鈕。
不幸的是,當使用這個標籤作為元資料的時候,訊息觸發器必須指定為一個字串。因為這個原因,你可能在這種情況下有時喜歡使用MXML配置:
<Object type="{SomeType}">
<CommandStatus property="isSaving" type="{SaveUserMessage}"/>
</Object>
這種配置風格沒有任何危險,重構後導致執行時錯誤,是由於元資料標籤某處埋藏了陳舊的限定類名。
7.7命令生命週期
就像前面的部分已經多次提到,託管命令在執行的時候被新增到上下文中。本節提供了一些更多的細節,這樣你就可以避免常見的錯誤比如在命令執行完成後試圖訪問Parsley的特性。
一個命令儲存在上下文的時間不會太久,因為他本來就是短生命週期的。只要它是上下文的一部分,它就不能得到垃圾收回收,因為上下文持有所有託管物件的引用。因此命令從上下文一旦刪除,容器將其視為已經完成。本節解釋確切含義。
7.7.1 同步命令
採取以下簡單(儘管很做作)的例子,一個用一個注入合作者同步命令:
class MultiplyCommand {
[Inject]
public var multiplier: Multiplier;
public function execute (msg: MultiplyMessage): int {
return multiplier.multiply(msg.valueA, msg.valueB);
}
}
命令用引發了這個命令的訊息傳遞的兩個整數執行一個簡單的乘法。不幸的是它不是最聰明的命令,所以它需要一個合作者(collaborator )來做實際的計算。
因為容器在命令執行之前新增到上下文,任何注入或其他設定在execute方法呼叫的時候已經完成。同樣,由於這是一個同步命令,我們也知道命令是短生命週期的。execute方法返回它的結果後,該命令將被從上下文刪除。因為一個命令支援全套容器服務,你也可以用第二種方式,標籤為[Destroy],然後你將看到,它將execute方法執行之後立即被呼叫。
7.2.2 非同步命令
現在讓我們想象注入的乘數不是最聰明的,它需要一些時間來執行計算。這意味著我們必須把命令轉換為非同步命令,要求一個回撥函式來獲得注入。
class MultiplyCommand {
[Inject]
public var multiplier: Multiplier;
public var callback: Function;
public function execute (msg: MultiplyMessage): int {
return multiplier
.multiply(msg.valueA, msg.valueB)
.addResultHandler(resultHandler);
}
private function resultHandler (result: int): void {
trace("before invoking callback");
callback(result);
trace("after invoking callback");
}
[Destroy]
public function destroy (): void {
trace("destroy method called");
}
}
如果你執行上述命令的traces將是:
before invoking callback
destroy method called
after invoking callback
傳遞結果到回撥方法也標誌著完成命令,這樣就可以從上下文刪除。如果你需要執行任何型別的需要用到容器特性(如傳送訊息)任務,你必須在傳遞結果之前那樣做。
Asynchronous Command based on AsyncTokens and Result Handlers
在其他的變體非同步命令中所描述的機制略有不同:
public class GetUserListCommand {
[Inject("myService")]
public var service: RemoteObject;
public function execute (): AsyncToken {
return service.getUserList();
}
public function result (users: Array): void {
// command still managed here
}
}
這裡框架將一直等待,直到AsyncToken產生一個結果或錯誤,然後呼叫結果處理程式。只有呼叫命令結果處理程式後,該命令才會從上下文中刪除。