Parsley开发指南 5 解耦绑定

5 解耦绑定(Decoupled Bindings)

2.3版本推出此功能,对现有解耦你的应用程序的方式添加了一个重要的新的选项,注入和消息传递。尽管这不是一个新的想法,相似的概念在Java中的Seam中已经存在了(基于@In @Out 的注解),并且也已经被其他的Flex框架使用了,比如GraniteDS提供了 [In][Out]元数据标签配置。

5.1 比较依赖注入和解耦绑定

本节的目的是概述两个框架之间的差异特征,如何确定在一个特定的用例中使用哪些机制,分享一些相似性和一些建议。

注意,还有其他框架甚至混合这两种机制行成一个单一的功能。但是我认为,最好是有明确的区别,两者的优点和缺点还是有显著的差异的。当这些功能有不同的标签的时候,它还使得代码更容易阅读。

依赖注入

更多查看 依赖注入

经典的依赖注入(用Parsley的[Inject] 标签),解耦绑定提供了以下好处

l 这个功能是为健壮性设计的。一个对象在一个合作者上声明一个依赖,并且当容器初始化的时候,它可以依靠容器注入一个匹配的实例。同样它将确保只要目标已经被注入了,那么被注入的对象至少有生命周期。这样依赖的对象会保证合作者不会突然’消失‘。这就是为什么你不能从子上下文注入到父上下文(当然反过来没什么问题),也不能注入动态对象(除非它们在上下文中是用 <DynamicObject>定义的),他们可以随时被删除。

l 对于增加健壮性来说同样有用的是,容器会保证当使用[Init] 标签的方法调用的时候,所有的注入已经被执行。这通常简化了内部初始化代码。

l 任何通过错误的配置引起的错误(含糊不清和缺失的依赖关系),会在初始化上下的时候检测到。

Decoupled Bindings

解耦绑定(Parsley[Publish][Subscribe]标签)在依赖注入上提供了以下优点:

l 这个特性比依赖注入更动态化。对任何完全没有值的subscriber 或者在生命周期中接受多个更新的同一个属性来说,这是完全合法的。解耦绑定跟Flex绑定的典型用法非常相似。

l 发布的对象甚至不需要是一个被容器管理的对象。所以这个特性可以用一种更轻量级的方式使用。

l 发布一个对象也不会引起反射对象(除非你指定它应该管理),所以它带来的性能好处。

当然相比注入来说依然有代价:

l 没有保证用 [Init]标签的方法调用的时候subscribers 是有值的,对象的值可以在任何时间点发布和更新。

l 错误配置造成的错误(特别是含糊不清)有时候不会在初始化上下文的时候检测到。它们可以随时生成一个new publisher 到上下文中((动态对象也可以作为publisher)。

5.2基本用法

在被管理的对象上设置一个publisher 你可以使用Publish 元数据标签:

[Publish][Bindable]

public var selectedContact:Contact;

在Flex应用程序中你还需要使用[Bindable]标签在publishing 端,因为Flex的实现是基于标准的Flex绑定架构的。关于Flash应用程序查看本章最后一部分。

一旦你更新发布者的属性,值会被推送到在任何上下文中的任何其他对象中匹配的订阅者。

[Subscribe]

public var selectedContact:Contact;

如果你不指定一个对象id,匹配订阅者将会由属性的类型决定,比如在本例的Contact。注入和消息传递这也对多态有用。你可以发布一个Dog,那么Animal 的订阅者也会得到更新。

如果可能的话,像其他的特性一样最好使用按类型匹配,避免使用字符串标识符。

如果你需要发布同一个类型的多个对象,你可以指定一个id:

[Publish(objectId="selectedContact")][Bindable]

public var selectedContact:Contact;

当然订阅者也必须指定相应的id。在某些情况下,你也可以使用作用域来限制对象发布的应用程序区域,避免在这种方式使用一个id。

你也可以让一个属性同时作为发布者和订阅者:

[PublishSubscribe][Bindable]

public var selectedContact:Contact;

现在当其他任何发布者更新的时候值会被更新,但是仍然本身就作为发布者。

最后,像其他大多数标签一样这些标签也可以用MXML和XML。

<Object type="{MyPublisher}">

    <Publish property="selectedContact"/>

<!-- other tags -->

</Object>

5.3 避免冲突

用一个可靠和健壮的方法来对解耦绑定实施工作,你必须确保你的设置不会导致模糊不清。框架必须在任何时间点都知道哪个发布者是“负责”哪个特定订阅者。通常发布到不同的作用域不能导致冲突,因为作用域(scopes )是完全相互隔离的。但对于任何同一类型或id(取决于你是否使用一个id)发布在单个作用域,规则如下:

l 你完全可以有一个[Publish] 标签,和任意数量匹配的 [Subscribe] 标签。

l 你可以有任意数量的[PublishSubscribe]标签和任意数量匹配的[Subscribe]标签。

l 只有一个[Publish]没有任何匹配的[Subscribe]也是合法的,反之亦然。如果一个[Subscribe]标签没有匹配的[Publish]的标签,它的值会被设置为null

当你在单个作用域看到相同的类型或id有多于一个的[Publish]标签,这是是非法的。对相同类型或id结合[Publish][PublishSubscribe]也一样。在这些情况下,框架将无法决定“谁负责”。这个问题不会发生在多个[PublishSubscribe]标签。因为他们也是订阅者,改变其中一个也将更新所有其他人,所以他们总是保持同步。

5.4 使用 Scopes

就像消息传递一样解耦绑定工具允许使用作用域。有时候你不想在全局发布一个对象,而是使用不同的实例在不同领域的应用。对于一般介绍,请参阅 6.10使用作用域在消息一章。

与消息传递有一个主要区别在默认的行为。消息传递默认发送方在上下文中所有可用的作用域中分派,所以接收方可以决定在哪些作用域接收。这是不同的,像前面一样发布常常会导致冲突(参见前面的部分)。相反,默认是只发布到全局作用域。

对于任何其他作用域,除了全局作用域必须使用scope属性显式指定:

[Publish(scope="window")][Bindable]

public var selectedContact:Contact;

当然所有订阅者也要:

[Subscribe(scope="window")]

public var selectedContact:Contact;

5.5 发布管理对象

在所有前面的例子发布的对象的状态并未改变。你可以使用容器管理的对象或任何其他容器不知道的对象,发布给订阅者。如果这是非托管它仍将如此。在某些情况下,你可能希望只有它被发表的时候,动态显式地添加对象到上下文中。这种方法也可以参与消息传递和其他容器功能。这与使用Context API来创建动态对象是同样的效果,展示在 8.7 动态对象,好处就是你不必跟框架API打交道。

这就是它如何工作的,你只需要添加托管属性在Publish标签:

[Publish(managed="true")][Bindable]

public var selectedContact:Contact;

现在任何时候你将这个变量设置为一个Contact 实例,,该实例自动被添加到上下文。它会被托管直到:

l 变量被设置了另一个Contact 实例,或者被设置null

l 用这个变量的对象从Context中移除了。

l 对象属于的上下文被摧毁了。

这也意味着用 [Init][Destroy]划分的方法的生命周期,可用于当对象被发布和移除时候,发布的对象来接收通知。

5.6 持久化属性

一个发布属性可以被标记为persistent。该对象被实例化后将立即得到最后一次应用程序运行的值。

[PublishSubscribe(persistent="true"objectId="selectedContact")]

public var selectedContactId : String;

在Parsley 引擎下,用 Local SharedObjects来储存值。但这仅仅是默认的实现,像Parsley几乎所有东西一样也可以切换,像下一节解释的一样。但是如果你只使用简单的类型,如字符串或数字,只在全局作用域(这是默认的)并且使用Local SharedObjects的持久化也没问题,那么你可以跳过本节的所有剩余部分。在这种情况下,上面所示的持久化属性就是你所有需要知道的。

不用Local SharedObjects自定义持久化

例如,如果你想在服务端的一个用户会话中存留值,你可以提供自己的实现从远程服务中读和写值。接口看起来像这样:

[Event(name="change" type="flash.event.Event")]

public interface PersistenceManager extends IEventDispatcher {

    

    function saveValue (scopeId:String baseType:Class id:String value:Object) : void;

    

    function deleteValue (scopeId:String baseType:Class id:String) : void;

    

    function getValue (scopeId:String baseType:Class id:String) : Object;

    

}

这个scopeId baseType id的值可以用来唯一地标识一个值。id本身并不保证是唯一的。你可以使用这三个值来构建一个唯一键发送到服务器。这个scopeId将后面解释。一个change事件必须通过实现派出,每当它接收来自服务器的新值(或无论它获得的数据来自哪)。这包括在应用程序启动时加载的值。一旦所有初始值是可用的,change事件应该被分派。

这样的一个自定义的服务可以注册像这样:

BootstrapDefaults.config.scopeExtensions

.forType(PersistenceManager)

.setImplementation(MyCustomPersistenceManager);

持久化复杂类型

框架只检测存储在持久化属性中实际引用的变化。这通常是Publish/Subscribe机制的工作方式。当一个已发布对象内部发生变化,不需要通知订阅者(而且没有明显的方式去通知),因为他们已经引用了修改后的对象。但对于持久属性这是有影响的,这意味着它适用于简单的属性如字符串或数字。因为对复杂类型来说内部变化得到持久化的确是也是期望的。因为服务器(或通用持久存储)在你的应用程序中并没有一个永久对象的引用,比如一个normal, local subscriber。

要检测复杂类型的改变,你不得写这些类型的代码到一个定制的PersistenceManager实现中。框架不容易知道哪些变化要观察(以及如何),所以最好是让应用程序开发人员来做。未来的版本可能会添加支持常见类型,至少(像一个ArrayCollection,它可能会监听CollectionEvents)。

使用作用域

当使用全局作用域的时候,持久值工不需要额外的设置步骤(这是默认的)。但是当你使用多个上下文并且想让他们每个都有持久化的值(使用本地作用域或一个自定义的作用域),那么当应用程序重新启动的时候,你需要注意重新创建相同的一组作用域和上下文。这通常发生在某种特定于应用程序的导航,总是传给PersistenceManager处理scopeId的逻辑。为了保证正确的传送本地持久值,Context需要用相同的id创建。通常你不关心作用域的id,只指定名字。但是如果需要显式地指定的id可以:

<parsley:ContextBuilder config="..."> 

<parsley:Scope name="someScopeName" uuid="{idFromLastSession}"/>

</parsley:ContextBuilder>

虽然名字不是唯一的(你可以有尽可能多的作用域,而名称相同的,只要他们不重叠),idscope来说的确是一个唯一的标示符。这是相同的值,然后传给得到的scopeId参数的PersistenceManager的所有方法。在持久性属性本身你仍然只需要使用这个名字,如果你需要传送uuid这将会变得非常复杂。

[PublishSubscribe(persistent="true" objectId="selectedContact" scope="someScopeName")]

public var selectedContactId : String;

框架简单地知道someScopeName作用域的特定实例的id。

关于作用域总体概述见6.10使用作用域

5.7 Bindings in Flash

Flash的就暂时省略了,以后有空补上。

你可能感兴趣的:(Parsley开发指南 5 解耦绑定)