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里避免使用switch、for、for 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()方法】
警告 注意:1、fp9.0.16不会广播Event.ADDED_TO_STAGE或者Event.REMOVED_FROM_STAGE。因此,mediatormap无法得知view是否已经被放置到contextView中,所以也不会自动地创建/销毁mediator。 2、Flash时间轴也很古怪——当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自身的视图逻辑,你应该使用三层的手段实现。