Chinaunix首页 | 论坛 | 博客
  • 博客访问: 3332072
  • 博文数量: 530
  • 博客积分: 13360
  • 博客等级: 上将
  • 技术积分: 5473
  • 用 户 组: 普通用户
  • 注册时间: 2006-07-13 13:32
文章分类

全部博文(530)

文章存档

2017年(1)

2015年(2)

2013年(24)

2012年(20)

2011年(97)

2010年(240)

2009年(117)

2008年(12)

2007年(8)

2006年(9)

分类:

2010-04-30 11:29:26

    parsley2引入了一个新的通用消息框架,允许你以完全解耦的方式在对象间传递消息。解耦意味着发送者与接收者可以相互不知道,同样重要的是,发送和 接收对象也完全从框架本身脱钩。这个优势被其它FLEX框架所忽略(包括parseley1),因此你不得不使用框架的对象或静态方法去发送程序的事件或 消息。如果你的对象与框架完全脱钩,你可以在不同的上下文中复用他们,那些上下文中可能你想使用不同的框架或根本就不想使用框架。比如你可能想在单元测试 中注入派发对象和接收一个实例,而不是进行额外的初始化。

    Parsley消息框架很通用,它没有规定一个特殊的使用情况。这与一些现有的FLEX MVC框架不同,这些框架主张使用一定的结构和使用模式,甚至提供了具体的controller,modelview部分。parsley2可以让你自由的设计你的应用框架。如果你打算使用消息框架去建立一个经典的MVC架构,你可以去读一下

    本章描述了如何配置对象去发送和接收消息。对于每一个为AS3的元数据,MXMLXML配置的配置选项的例子也包括在内。

5.1 派发消息(Dispatching Messages)

对于一个由parsley管理的想派发消息的对象,你可以阅读下列选项:

  • 如果派发对象是一个普通的EventDispatcher
  • 当你的消息不是事件的子类
  • 特殊情况下,你需要使用框架的API

5.2 接收消息(Receiving Messages)

对于一个由parsley管理的想接收和处理消息的对象,你可以阅读下列选项:

  • 派发一个特殊的消息,则调用该方法
  • ) 派发一个特殊的消息,则设置属性
  • ) 在消息被处理或绑定之前,进行拦截,有选择地取消或推迟并重新派发消息
  • 处理由其它处理器、绑定器或拦截器引发的错误
  • ) 处理异步操作和它们的结果和错误
  • 通过一个对象创建去执行一个异步命令,执行完后销毁.
  • ) 特殊情况下,你可以使用框架的APE去进行各种注册

5.3 托管事件

如果你想派发由parsley托管的消息是一个普通的EventDispatcher,只需要下面二步:

  • [Event]标签声明你想派发的事件。这是一个非常好的最佳实践,不管你是否使用parsley,因为这样提高了类的可读性,ASDoc将输出所有用这个标签声明的事件。
  • 告诉parsley声明的事件中哪些应该被"管理",这对消息接收者十分有用。你可以使用AS3 Metadata, MXML XML tags编写。

Metadata Example

[Event(name="loginSuccess",type="com.bookstore.events.LoginEvent")]
[Event(name="loginFailed",type="com.bookstore.events.LoginEvent")]
[Event(name="stateChange",type="flash.events.Event")]
[ManagedEvents("loginSuccess,loginFailure")]
public class LoginServiceImpl extends EventDispatcher implements LoginService {

[...]

private function handleLoginResult (user:User) : void {
dispatchEvent(new LoginEvent("loginSuccess", user));
}

}

    这个例子中,service声明了三个事件。其中的两个(loginSuccessloginFailure)是应用程序事件,应该由parsley托管,派发这些事件有关的所有对象。第三个事件是一个低级事件,仅关注于与那个service直接交互的对象,这些对象只要注册一个普通的事件监听器。

    这个例子只有一个方法,显示了如何将结果转换成一个事件并派发它。因为loginSuccess被声明为了托管事件,它将通过parsley传递给所有的MessageHandlers.

MXML Example




    如果你在MXML中声明了一个托管事件,你可以忽略这里的[ManagedEvents]元标签。注意,你所写的代码中必须包含[Event]元数据标签,因为那是用于表示flash API的。

      XML Example

        


5.4 注入消息派发器(Injected MessageDispatchers)

    有时候你不想用通过事件调用模式开发。有时一些特殊场景的事件,parsley并不能"bubble"stopPropagationparsley消息时序处理过程中没有任何作用。你甚至想避开消息接收者通过event.target捕获住消息的派发器。

    在这些情况下,parsley提供了使用任何一个类作为应用程序消息,而不管这个类是否继承了flash.event.Evnet。你可以请求这个框架注入一个消息派发器去派发自定义的应用程序消息。假设你创建了下列简单消息类:
class LoginMessage {

public var user:String;

public var role:String;

function LoginMessage (user:
String, role:String) {
this.user = user;
this.role = role;
}

}

你可以在service类中使用如下代码:

    public class LoginServiceImpl implements LoginService {

[MessageDispatcher]
public var dispatcher:Function;

public function login(
user:String, role:String) : void {
dispatcher(new LoginMessage(user));
}

}

    注意你的service类不用扩展EventDispatcher。这里只需要声明一个类型属性变量,并用[MessageDispatcher]标识,它将指示parsley在对象创建时,注入一个消息派发函数。你可以传递任何类型的对象给这个派发函数。

     MXML Example

           


     XML Example

           


    如果你不想使用元数据标签,你可以通过MXMLXML配置。

5.5 消息处理器(MessageHandlers)

    消息处理器是接收端最常见的方法。当一个特殊的应用程序消息被捕获,你可以声明方法进行调用。通常,这个方法仅通过参数类型选择:

    Metadata Example

     [MessageHandler]
public function handleLogin (message:LoginMessage) : void {

    无论何时一个匹配的消息类(包括子类)被捕获,将调用这个方法。

    MXML Example

     


    XML Example

     


    这里还可以处理一个特殊情况,你分解一个消息的属性作为不同的消息处理器的参数:
     [MessageHandler(type="com.bookstore.events.LoginMessage",messageProperties="user,role"]
public function handleLogin (user:User, role:String) : void {

注意:你必须声明消息类型,因为它不能由参数类型检查

    最后,你可能遇到消息类的选择不足的问题。如果你在不同的场景和应用程序派发同样的消息类,你可能想更深入的定义消息选择过程。参见详细描述。

5.6 消息绑定器(MessageBindings)

消息绑定一般用于以下场景:

    直接把一个消息类中的属性赋给目标类的属性,一般过程是先声明一个消息处理函数,通过MessageHandler绑定该函数,然后再函数内将消息类的属性赋给相应属性,过程太麻烦。

    如果你要将一个类的属性和一个消息属性绑定,消息绑定就很方便,即当一个匹配的消息类被捕获,框架自动根据消息属性更新类的属性。在下面的例子为例,用户属性与LoginMessage类的属性绑定,如下例:

    Metadata Example

         [MessageBinding(messageProperty="user",type="com.bookstore.events.LoginMessage")]
public var user:User;

    MXML Example

        
targetProperty="user"
messageProperty="user"
type="{LoginMessage}"/>

    XML Example

        
target-property="user"
message-property="user"
type="com.bookstore.events.LoginMessage"/>

    如果你想在MessageHandlers中使用MessageBindings的选择功能,参见

5.7 消息拦截器(MessageInterceptors)

    这是接收端的第三种选择。当应用程序状态或用户自己决定消息是否传给处理器和绑定器时, 可通过拦截器实现。拦截器具有下列特征:

  • 所有注册的拦截器在处理器或绑定器之前执行
  • 拦截器可以有选择的中止消息处理和在之后的某个时刻恢复

例:当你有未登录就可以访问的应用程序,一些行动没有登录则不让操作。在这种情况下可以暂停拦截消息处理,显示一个登录对话框,并成功登录后恢复的消息处理。

另一个例子:在任何一个删除操作之前显示一个简单的警告,通常如下:

public class DeleteItemInterceptor {

[MessageInterceptor(type="com.bookstore.events.ShoppingCartDeleteEvent")]
public function interceptDeleteEvent (processor:MessageProcessor) : void {
var listener:Function = function (event:CloseEvent) : void {
if (event.detail == Alert.OK) {
processor.proceed();
}
};
Alert.show("Do you really want to delete this item?", "Warning",
Alert.OK | Alert.CANCEL, null, listener);
}

}

    当用户点击取消按钮时,MessageProcessor将不会恢复执行,但处理器或绑定器将被执行。

    正如MessageBindings,你必须声明消息类型,因为它不能根据方法签名检测。拦截方法总是有一个简单的MessageProcessor类型参数。你也可以使用MXML或XML代替元数据标签进行声明。

    MXML Example

          
method="interceptDeleteEvent"
type="{ShoppingCartDeleteEvent}"/>

    XML Example

          
method="interceptDeleteEvent"
type="com.bookstore.events.ShoppingCartDeleteEvent"/>

拦截作为消息框架的一个特征,框架必须传递MessageProcessor实例给你,你就能反控或处理消息


5.8 错误处理器(Error Handlers)

    在2.1版本后,你可以设置一个方法去处理或拦截消息引发的错误:

     [MessageError(type="com.bookstore.LoginEvent")]
public function handleError (processor:MessageProcessor, error:IOError) : void;

    上例中,当LoginEvent类型的消息抛出一个IOError错误,这个方法将进行处理。你也可以创建一个全局的错误处理方法,接收任何消息引发的错误类型

     [MessageError]
public function handleError (processor:MessageProcessor, error:Error) : void;

    最后,因为错误处理器通过标签只能监听单一的范围,你可以添加一个错误处理器自动附加到每个应用程序中。你可以通过GlobalFactoryRegistry实现:

     var provider:ObjectProvider = Provider.forInstance(this);
var handler:MessageErrorHandler
= new DefaultMessageErrorHandler(provider, "handleError", Object);
GlobalFactoryRegistry.instance.messageRouter.addErrorHandler(handler);

5.9 异步命令方法(Asynchronous Command Methods)

    2.2版本的parsley框架也包含了几个由cairngorm3项目提出的设计思想。详见 .

    标签集不仅不包括[MessageHandler]标签,还包括多个不同类型的标签进行消息处理。通过[Command]标签,你可以让框架去管理异步命令的执行:

     [Inject]
public var service:RemoteObject;

[Command(selector="save")]
public function saveUser (event:UserEvent) : AsyncToken {
return service.saveUser(event.user);
}

    [Command]标签支持与[MessageHandler]标签相同的属性集。匹配消息的逻辑也一样。上例中,捕获UserEvent(包括子类)后,命令将执行,选择器save通常是指事件类型。

    所不同的是,命令不要求有一个像普通消息处理器需要一个void返回类型。相反,它可以指定一个类型,该框架可以用它来为你管理异步命令执行。在这种情况下,它是这是由远程调用返回AsyncToken。现在,该框架还将为你管理的最终结果或错误。现在,任何对象都可以包含一个方法,用于处理调用的结果:

        [CommandResult(selector="save")]
public function handleResult (user:User, event:UserEvent) : void {

    上例中,User实例由远程调用返回,与触发动作的原始消息一起传给结果处理器。第二个参数是可选的,如果你忽略它,你必须用标签描述消息类型,以进行精确匹配:

        [CommandResult(type="com.foo.events.UserEvent" selector="save")]
public function handleResult (user:User) : void {

    这是必要的,就象普通消息处理器一样,parsley独立依赖于目标选择器的字符串标识符(事件类型)。它总是消息类型的混合体和带选项的选择器。

    结果参数也是可选的,可以简单地提交:

           [CommandResult(type="com.foo.events.UserEvent" selector="save")]
public function afterUserSaved () : void {

    或者它可能是ResultEvent而不是实际结果值,如果你需要访问从服务器端返回的头部信息,可写成下面代码:

          [CommandResult(selector="save")]
public function handleResult (result:ResultEvent, trigger:UserEvent) : void {

    为了接收错误事件,其它标签也可以使用:

          [CommandError(selector="save")]
public function handleResult (fault:FaultEvent, trigger:UserEvent) : void {
     [CommandError]参数也是可选的,匹配规则也[CommandResult]标签一样。

    最后,你也可以观察执行命令的状态:

          [CommandStatus(type="com.foo.events.UserEvent" selector="save")]
public var isSaving:Boolean;

    如果一个或多个异步命令匹配描述的类型,并且选择器正在执行,这个Boolean类型总是True,否则为false。这个对于任务处理很方便,比如在命令执行期间将按钮置灰

可支持的命令类型

    上面的例子展示了执行远程调用的例子和返回一个AsyncToken。命令模式也可用于Cinnamon Remoting,返回ServiceRequest实例而不是FLEX的RemoteObjects。最后,命令模式与spicelib任务框架集成,一个命令也可将任务作为返回类型。

    你可以通过你自己的CommandFactories扩展框架的限制,创建更多的命令标签,接口如下:

public interface CommandFactory {

function createCommand (returnValue:Object,
message:Object, selector:* = undefined) : Command;

}

    方法createCommand的返回值是一个方法,该方法用于执行命令(比如AsyncToken),选择器触发命令。它也返回一个Command实例,大多数是AbstractCommand的子类,工厂实现类必须进行注册:

    GlobalFactoryRegistry.instance.messageRouter
.addCommandFactory(MyReturnType, new MyCommandFactory());

addCommandFactory的声明如下:

    function addCommandFactory (type:Class, factory:CommandFactory) : void;

   它描述的命令的返回类型和工厂实例本身。

5.10 短生命周期命令对象(Short-lived Command Objects)

    上节命令模式已展示了对象执行命令不会发生任何改变。标签[Command], [CommandResult][CommandError]可以用于任何一个托管对象的方法。它可以是一个单例对象或者一个视图。每个对象可以包含多个命令方法和多个结果处理器。结果处理器可能是相同对象或不同对象。总之,这些标签仅控制单个方法或属性。

    框架允许定义一个命令。如果不应用标签给个单个方法,你可以对一个完整的命令对象进行配置。这个对象仅是单个方法,用于执行命令,或者是一个接收结果的方法和一个事件型的错误方法。结果处理器仅被执行同一个实例的命令所调用,这个命令对象可能象这样:

public class SaveUserCommand {

[Inject]
public var service:RemoteObject;

public function execute (event:UserEvent) : AsyncToken {
return service.saveUser(event.user);
}

public function result (user:User) : void {
// do something with the result
}

public function error (fault:Fault) : void {
// do something with the result
}

}

    在配置类中定义[DynamicCommand]标签:

            

    这是一个最小的配置,描述了选择器和消息类型。而且我们不一定在命令对象中使用常规的命令元数据标签。上节展示了选择方法中命令处理器、结果处理器和错误处理器的默认名,你也可以用DynamicCommand标签重写。下例我们重写这三个方法名:

            type="{SaveUserCommand}" 
selector="save"
execute="saveUser"
result="handleUser"
error="handleFault"
/>

    标签也支持[MessageHandler][Command] 标签的所有属性:

            type="{SaveUserCommand}" 
selector="save"
scope="local"
order="1"
/>

    上例中,当触发消息被同一上下文的对象捕获,命令将先执行。order用于决定执行的顺序,但不能决定结果处理和错误处理的顺序。

    除了特殊语法,DynamicCommand标签也允许象正常对象标签一样,拥有相同的子标签集:

       




    当然,命令对象可以由其它对象的结果处理器组成。如果你使用了[CommandResult]标签,这与在一个方法上标识[Command]标签,或者DynamicCommand标签声明过的命令对象,其效果是一样的。尽管同一个命令对象拥有private结果处理器,任何一个对象都可以接收到结果。

命令对象生命周期

    默认是容器为每个匹配消息创建一个新的命令实例。多个命令对象可以并发执行,并且不进行任何交互。如果只想使用一个实例,通过设置stateful属性修改:
        stateful="true"/>

    现在,当第一个匹配消息被捕获,实例才被实例化,陏后,该实例将作为一个容器托管对象,当捕获新的消息后,该实例被恢复,以节省对象创建和初始化时间。对于有状态命令,你不能用私有属性去保存信息,因为多个命令将相互交互,并且覆盖那个值。

5.11自定义选择器(Using Selectors)

    本例主要与MessageHandlers相关,MessageBindings和MessageInterceptors匹配方法或属性总是由消息类型决定。有时候,如果你在不同的场景或应用程序states中派发相同的消息,就出出现问题。你必须重新定义选择过程。

    如果你使用了Event类的类型属性作为选择器:

     [MessageHandler(selector="loginSuccess")]
public function handleLogin (message:LoginEvent) : void {
[...]
}

[MessageHandler(selector="loginFailure")]
public function handleError (message:LoginEvent) : void {
[...]
}

    上例中,LoginEvent实例的类型属性值为loginSuccess,handleLogin方法将被调用。

    对于自定义类型,不用扩展flash.events.Event。默认的选择器属性可以在消息类的属性上,用[Selector]元标签声明。

class LoginMessage {

public var user:User;

[Selector]
public var role:String;

[...]
}

    现在,你可以根据用户的角色,选择消息处理器:

    Metadata例子

         [MessageHandler(selector="admin")]
public function handleAdminLogin (message:LoginMessage) : void {

    MXML 例子

         


    XML 例子

         


5.12 使用范围(Using Scopes)

    Parsley 2.0中,每个子上下文可以共享父上下文的消息路由。消息总是全局进行派发。这种做法将简化模块化开发和弹出窗体等。scope的引入将更灵活,允许派发上下文的子集。

全局和局部的范围

    对于Parsley 2.1的默认范围是全局的范围,应用程序启动只创建一个根上下文,每个上下文根据全局范围创建,并没有父上下文,但可以被子上下文共享。另外,每个上下文将创建自己的局部上下文,这些可以不被子上下文共享,如下图所示:

   因为全局范围唯一性,它是所有的配置标签的默认范围。

            [MessageHandler(selector="save")]
public function save (event:ProductEvent) : void {

   处理器监听任何一个上下文派发的ProductEvents事件。

            [MessageHandler(selector="save", scope="local")]
public function save (event:ProductEvent) : void {

    现在,处理器仅监听相同上下文派发的事件。当然,scope属性可用于所有的消息接收类型,比如MessageInterceptor MessageErrorHandler。

    对于发送端,默认行为是不同的。对于每个[ManagedEvents]标签,scope并没有精确描述消息发送过程中经历的上下文。接收端可以决定它想监听的范围,如果你想限制发送端的范围,你可以在[ManagedEvents]标签内使用属性scope:

    [ManagedEvents("save,delete", scope="local")]


自定义范围

    如果你即不想用全局消息,也不想用局部消息,你也能创建你自义的范围。下面假设你开发一个大型的AIR项目。根窗体组件可能以根应用上下文作为基础,创建一个新的上下文,这个窗体的部分组件也可以包含一些子上下文。你可以象下图所示,创建自定义上下文:

    窗体范围是自定义的,存在两个并列的默认范围。现在,如何构建框架去创建范围?还是应该根据根上下文创建,可以用MXML描述如下:







    或者添加scope到CompositeContextBuilder:

    var builder:CompositeContextBuilder = new DefaultCompositeContextBuilder(viewRoot);
builder.addScope("window", true);
FlexContextBuilder.merge(MyWindowConfig, builder);
builder.build();

    addScope的第一个参数是scope的名称,该名称必须唯一。这就允许你在消息处理器上定义scope的名称。

            [MessageHandler(selector="save", scope="window")]
    public function save (event:ProductEvent) : void {

    第二个boolean参数表示子上下文是否能共享scope。

5.13 使用消息API(Using the Messaging API)

    通常应用代码应避免直接与parsley API交互。但是特殊情况下或你想扩展框架,建立另一个新框架时,你可能需要注册消息处理器或自动绑定。MessageReceiverRegistry接口包含下列方法:

    function addTarget (target:MessageTarget) : void;
function addInterceptor (interceptor:MessageInterceptor) : void;
function addErrorHandler (handler:MessageErrorHandler) : void;
function addCommand (command:CommandTarget) : void;
function addCommandObserver (observer:CommandObserver) : void;

    消息接收器分成五类:MessageTarget是普通接收器,实现类包括MessageHandlerMessageBinding。MessageInterceptor与同名的标签对应,MessageErrorHandler与标签[MessageError]对应。CommandTarget是一个异步命令的target,CommandObserver监听结果或错误。接口包括五个方法,用于添加这五类接收器。

    为了获取MessageReceiverRegistry实例,你可以注入上下文实例到你的类中。你必须先确定你的接收器的scope,下面的例子是注册一个消息处理器为全局scope:

class SomeExoticClass {
[Inject]
public var context:Context;

[Init]
public function init () : void {
var registry:MessageReceiverRegistry
= context.scopeManager.getScope(ScopeName.GLOBAL).messageReceivers;
var target:MessageTarget
= new MessageHandler(Provider.forInstance(this), "onLogin");
registry.addMessageTarget(target);
}
}

    当你在Context上设置了[Inject]标签,parsley将注入上下文实例给这个类。

    最后,你可以使用ScopeManager派发消息:

     context.scopeManager.dispatchMessage(new LoginMessage(user, "admin"));

    当直接通过ScopeManager派发,消息将经过该context管理的所有scope。这样接收端可以决定哪个scope去监听。万一你不想让发送端选择,你必须通过个体的scope派发:

    var scope:Scope = context.scopeManager.getScope(ScopeName.LOCAL);
scope.dispatchMessage(new LoginMessage(user, "admin"));



阅读(3420) | 评论(1) | 转发(0) |
给主人留下些什么吧!~~

chinaunix网友2010-11-09 10:46:07

当用户点击取消按钮时,MessageProcessor将不会恢复执行,但处理器或绑定器将被执行。 我认为此处翻译的不准确,与原来意思正好相反。处理器或绑定器将不会被执行