Flash/Flex 框架简介—PureMVC
简介
此框架比较有名,用的人也应该比较多,所以比较成熟,里面的思路也比较清晰,使用本身实现的消息机制,不依附于as的事件驱动,故此有很多种语言的版本。
一. 思路
1.参考资料
列举一下pureMVC里面使用到的设计模式以,可以先略过,后面涉及到再回过来查看即可.都是书本的定义, google一下会有很多资料.
- 命令模式(command):
将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;
对请求排队或记录请求日志,以及支持可撤销的操作。 - 观察者模式(Publish/Subscribe):
定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。
- 代理模式(proxy):
为其他对象提供一种代理以控制对这个对象的访问.
- 外观模式(facadee):
为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
- 中介者模式(Mediator):
用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显示的相互引用,从而使其耦合松散,而且可以独立的改变它们之间的交互。
2.框架思路及其执行流程简述
pureMVC顾名思义采用了mvc的架构,将系统分为三层,其操作逻辑层主要由command实现,持久层由proxy来实现与服务器交互及客户端缓存数据,视图层由mediator封装,有利于逻辑划分视图模块,封装视图组件之间的交互。
其三层的通信采用框架本身采用Publish/Subscribe方式实现的事件机制来驱动,并由facade统筹三层在框架实际使用中的实现。
总的来说,pureMVC思路清晰,而且采用了框架本身的消息机制,并没有打乱as本身的事件驱动,两种事件机制融合运用,设计得当的话应该是可以做出解耦的很好的系统。但在实际使用中pureMVC的确也存在不少问题,譬如command模式导致零散的command类增多,有点不切实际,mediator过重等,但目前还没有其他更好的思路替代,所以大多数的as项目都有采用puremvc框架或者采用其类似的思路。
3.源码分析
Puremvc的源码目录结构也十分清晰。
interfaces里面是一些接口,接口的名称已经使人望名知意了,使用者可以实现这些接口来完成自己定制的功能,后面会考虑带出一点思路,这里暂不做叙述。
Pattens里面是上面说到的几个设计模式的实现,我们将从这个目录说起,并且联合core里面对mvc架构具体实现的内容,对puremvc的源码进行一下分析。(对下面列举的模式有疑惑的同学,请参考一下上面提过一下的模式简介,或者google一下)
- Command
在puremvc里面提供了2种command,分别为SimpleCommand和MacroCommand,显然SimpleCommand是对应一个消息进行的处理,在创建具体的commmand的时候需要覆盖SimpleCommand的execute方法,而MacroCommand则是加载了一个SimpleCommand的队列,按先进先出的顺序执行队列中的SimpleCommand.无论是SimpleCommand和MacroCommand都需实现Icommand接口的execute方法,以便在控制器中能有统一的方法来执行逻辑操作。
MacroCommand的execute方法:
代码1 public final function execute( notification:INotification ) : void{
2 while ( subCommands.length > 0) {
3 var commandClassRef : Class = subCommands.shift();
4 var commandInstance : ICommand = new commandClassRef();
5 commandInstance.execute( notification );
6 }
7 }Command模式在构思中是不错的,将逻辑操作分离了出来,使程序的耦合进一步降低,但在实际的使用中还是会有点矛盾,因为如果对每一个时间都去写一个command的话,显得太过琐碎,消息名与command的过量反而难以管理,故此还是要在实际项目中结合自身情况去考虑如何使用。
- Proxy
Proxyde的作用起码有2个,一是处理与服务器端交互的代码,封装为proxy中的公开方法,供实际程序中调用,以便日后维护。二是做数据的 持久,将数据缓存在proxy中,以便各模块使用。pureMVC中proxy固有的东西不多,不做太多叙述。然而由于pureMVC中并没有带有对服务器交互的模块,故此在与服务器通信的层次上需要在项目实际中根据需求做一下处理。
- Mediator
Mediator是协调视图对象的中介者。是在视图层上再抽象出一个结构,将逻辑相关的视图对象放置在一个mediator里面,在mediator里面对其各视图对象的交互做封装,最后公开为在mediator里面对某个消息的响应或者方法。
其listNotificationInterests罗列的消息为该mediator关注的消息, handleNotification则是对接受消息之后的处理,在实现handleNotification时推荐的是使用switch“`case对所接受到的消息进行分支处理。至于mediator是如何接收到消息的,会在后边有所表述。
代码示例
假设某个mediator对消息名为”Event1″和消息名为”Event2″的2个消息感兴趣,可能会有以下代码.
代码1 public function listNotificationInterests():Array{
2 Return [
3 "Event1","Event2"
4 ];
5 }
6 //......
7 public function handleNotification( notification:INotification ):void {
8 switch(notification.getName()){
9 case "Event1":
10 //处理
11 break;
12
13 case "Event2":
14 //处理
15 break;
16 }
17 }
18 //......在实际的操作中感觉这种做法无疑是有利于模块化的开发与多人分工协作。模块与模块之间只需注意其消息的交互,而不用理会其里边的实际逻辑,在划分好功能模块后,使得多人合作开发能够更好的执行。
但在实际使用过程中仍感觉有不足之处,譬如mediator会过重的现象,由于每个消息都对应一个command的话会过于繁琐,如果把太多的逻辑放在在mediator里面做处理的话,一不小心就可能会导致mediator太庞大,而且由于mediator是做单例来使用,其里边引用的视图对象也要注意其生命周期,以便flashplayer能对其进行垃圾处理.
- Observer
观察者模式的实现是贯通这个框架的关键。下面对该包下的三个类:Notification,Notifier,Observer进行一下讲述。
Notification是在pureMVC中传递的消息体,其name属性定义了唯一的消息名,其data属性允许在消息体中附加数据,使得框架的各部分相互作用变得更生动。
Notifier是消息的发送者,关键是sendNotification这个方法,不妨回过头去看一下command,proxy,mediator,其实这三个类都继承了Notifier,故此在三者中都可对消息进行传递。
Observer是重点,注意到此类有2个关键属性,notify与context,以及一个关键的方法notifyObserver,籍由apply,进行对消息响应的统一封装。(如对apply不甚了解,可查阅一下as的api即可)。
Observer的notifyObserver,注意apply的使用
1 Public function notifyObserver(notification:Inotification):void{
2 This.getNotifyMethod().apply(this.getNotifyContext(),[notification]);
3 } - Facade
Facade作为一个外观,用于统筹整合mvc三层的实现,在讲述Facade之前,不妨先看一下core这个包。从简单说起,先看一下
Model: 数据持久层,在pureMVC中管理proxy的注册与卸载。其具体实现要注意的是registerProxy和retrieveProxy这两个方法,分别用以注册与获取已注册的proxy。
注册proxy
1 public function registerProxy( proxy:IProxy ) : void{
2 proxyMap[ proxy.getProxyName() ] = proxy;
3 proxy.onRegister();
4 }获取proxy
1 public function retrieveProxy( proxyName:String ) : IProxy{
2 return proxyMap[ proxyName ];
3 }其注册方式也很简单,即在Model内部持有一个proxyMap,作为以proxy名字为索引的哈希表用以缓存proxy实例,每注册一个proxy就在哈希表里添加一个对应项而已。
所以在model注册了的proxy如果不进行手动卸载的话,其生命周期与model的生命周期是相同的。Controller: 控制层,主要用以处理逻辑事务。
主要实现需注意registerCommand和executeCommand这2个方法。
注册command
代码1 public function registerCommand( notificationName : String, commandClassRef : Class ) : void{
2 if ( commandMap[ notificationName ] == null ) {
3 view.registerObserver( notificationName, new Observer( executeCommand, this ) );
4 }
5 commandMap[ notificationName ] = commandClassRef;
6 }执行command
代码1 public function executeCommand( note : INotification ) : void{
2 var commandClassRef : Class = commandMap[ note.getName() ];
3 if ( commandClassRef == null ) return;
4
5 var commandInstance : ICommand = new commandClassRef();
6 commandInstance.execute( note );
7 }registerCommand使得消息与command关联起来,缓存在一个以消息名与command的类型作为键值关联的哈希表中。
(注意到registerCommand的第二个参数类型为Class,由于as3根本上还是基于原型的,把Class也看作是一种对象类型即可。)
当接收到感兴趣的消息之后,Controller执行executeCommand方法,在该哈希表中查找到相应command的Class,然后创建一个该Class的实例,并执行该command的execute,实施command需要的操作。View:视图层,pureMVC的消息机制实现就放在这里,分量比较重。
需要注意的是registerObserver,notifyObservers,registerMediator这3个方法。
注册Observer
代码1 public function registerObserver ( notificationName:String, observer:IObserver ) : void{
2 var observers:Array = observerMap[ notificationName ];
3 if( observers ) {
4 observers.push( observer );
5 } else {
6 observerMap[ notificationName ] = [ observer ];
7 }
8 }通知Observer
-
代码1 public function notifyObservers( notification:INotification ) : void{
2 if( observerMap[ notification.getName() ] != null ) {
3
4 // Get a reference to the observers list for this notification name
5 var observers_ref:Array = observerMap[ notification.getName() ] as Array;
6
7 // Copy observers from reference array to working array,
8 // since the reference array may change during the notification loop
9 var observers:Array = new Array();
10 var observer:IObserver;
11 for (var i:Number = 0; i < observers_ref.length; i++) {
12 observer = observers_ref[ i ] as IObserver;
13 observers.push( observer );
14 }
15
16 // Notify Observers from the working array
17 for (i = 0; i < observers.length; i++) {
18 observer = observers[ i ] as IObserver;
19 observer.notifyObserver( notification );
20 }
21 }
22 }
23注册Mediator
先看一下registerMediator,前面曾经叙述mediator需实现listNotificationInterests方法,以列举该mediator感兴趣的消息。
在registerMediator则是通过遍历listNotificationInterests方法返回的数组,对该mediator感兴趣的每一消息创建Observer,并调用registerObserver将消息与Observer之间的对应持久在一个哈希表中。而notifyObservers则允许在pureMVC下的消息进行广播,是关键所在。
须注意到在pureMVC中的controller和mediator都是科技接收到消息的,(proxy只能发送消息,而不能接收)。
联系controller的registerCommand方法以及view的registerMediator方法,可知无论在controller注册的command或者在view注册的mediator,对事件的相应形式都是以Observer统一起来,放置在view中的一个哈希表中,故此,在view的notifyObservers方法中,其实就是遍历消息与Observer关联的哈希表,当Observer对目前发送的消息感兴趣时,则使用apply的方式调用其保存的方法函数,执行实际在command或者mediator的相应方法。理解core包的controller,proxy,view 3层的实现之后,facadee里面的代码实现就十分容易了,facadee只是做为一个外壳,统一管理3个层次的实现,这里就不多叙述了。
需注意到controller,proxy,view,facadee都使用了单例,故此也应注意command,proxy,mediator的注册与卸载,避免内存的滥用。
二 小结
至此,对pureMVC的介绍暂告一段落,不可否认pureMVC是一个优秀的mvc框架,思路结构清晰明了,至于评判其在使用过程中的不足之处,可能还是要根据实际项目以及个人习惯来考虑,也不好有一个统一的定论。
而且鄙人觉得一个框架的出现最难得的是提供了一个解决问题的好思路,了解了这种思路之后,代码的实现倒是可以自己根据实际需要加以更改。
如果没有自己整合的一套思路框架的话,鄙人觉得pureMVC仍是一个不错的选择。代码1 public function registerMediator( mediator:IMediator ) : void{
2 // do not allow re-registration (you must to removeMediator fist)
3 if ( mediatorMap[ mediator.getMediatorName() ] != null ) return;
4
5 // Register the Mediator for retrieval by name
6 mediatorMap[ mediator.getMediatorName() ] = mediator;
7
8 // Get Notification interests, if any.
9 var interests:Array = mediator.listNotificationInterests();
10
11 // Register Mediator as an observer for each of its notification interests
12 if ( interests.length > 0 )
13 {
14 // Create Observer referencing this mediator's handlNotification method
15 var observer:Observer = new Observer( mediator.handleNotification, mediator );
16
17 // Register Mediator as Observer for its list of Notification interests
18 for ( var i:Number=0; i< interests.length; i++ ) {
19 registerObserver( interests[i], observer );
20 }
21 }
22
23 // alert the mediator that it has been registered
24 mediator.onRegister();
25 }
26