目前,较主流Flex框架大致可分为两大类,一类是正规军MVC--Cairngorm&pureMVC,一类是自由军MVC兼IOC--swiz、mate、parsley、spring as。
正规军的MVC是在既定模式下运作整个流程,而这个模式的形成是以解耦为原则的职责划分,优点自然是权责清晰,而其不足在于过于正规化使得工作量或效率被转嫁,一个看似小小的需求,也要动辄整个流程,使得小需求付出过多(即程序员编码量过大)。
在这里,简单认识一下正规军中的pureMVC,认识她的一般处理流程、她的优缺点以及她可能的折中。
pureMVC一般处理流程如下图:
特点:①MEDIATOR耦合VIEW在于解耦VIEW与其它部分的耦合性,通过MEDIATOR直接对VIEW数据收集与更新负责;
②MEDIATOR、COMMAND以及PROXY直接通过通知解耦,并合理完成后台调用后反馈数据更新;
代价:①强松耦合加重通信次数;②带反馈数据的通信加重通信负担;
折中:采用面向接口编程/依赖注入,变强松耦合为松耦合,同时可减少通信次数;
接下来,用代码陈列的方式来感性认识pureMVC的一般处理流程(样例主线:model-view-mediator-command-proxy-other command):
Example
用户与View Component和Mediator的交互
假如有一个含表单的LoginPanel组件。对应有一个LoginPanelMediator,负责与LoginPanel交互并响应它的输入信息发送登录请求。
LoginPanel和LoginPanelMediator之间的协作表现为:LoginPanel在用户输入完信息要登录时发送一个TRY_LOGIN的事件,LoginPanelMediator处理这个事件,处理方法是发送一个以组件包含的LoginVO为“报体”的Notification(通知)。
LoginVO.as
package com.me.myapp.model.vo { //把这个AS3 VO映射到Remote Class [RemoteClass(alias="com.me.myapp.model.vo.LoginVO")] [Bindable] public class LoginVO { public var username: String; public var password: String; public var authToken: String;//登录权限允许时服务器设置此值 } }
LoginPanel.mxml:
<?xml version="1.0" encoding="utf-8"?> <mx:Panel xmlns:mx="http://www.adobe.com/2006/mxml" title="Login" status=”{loginStatus}”> <!— The events this component dispatches. Unfortunately we can’t use the constant name here, because MetaData is a compiler directive --> <mx:MetaData> [Event('tryLogin')]; </mx:MetaData> <mx:Script> <![CDATA[ import com.me.myapp.model.vo.LoginVO; // 表单项与LoginVO对象的属性双向绑定。 [Bindable] public var loginVO:LoginVO = new LoginVO(); [Bindable] public var loginStatus:String = NOT_LOGGED_IN; //义定Event名称常量 public static const TRY_LOGIN:String='tryLogin'; public static const LOGGED_IN:String='Logged In'; public static const NOT_LOGGED_IN:String='Enter Credentials'; ]]> </mx:Script> <mx:Binding source="username.text" destination="loginVO.username"/> <mx:Binding source="password.text" destination="loginVO.password"/> <!—The Login Form --> <mx:Form id="loginForm" > <mx:FormItem label="Username:"> <mx:TextInput id="username" text="{loginVO.username}" /> </mx:FormItem> <mx:FormItem label="Password:"> <mx:TextInput id="password" text="{loginVO.password}" displayAsPassword="true" /> </mx:FormItem> <mx:FormItem > <mx:Button label="Login" enabled="{loginStatus == NOT_LOGGED_IN}” click="dispatchEvent( new Event(TRY_LOGIN, true ));"/> </mx:FormItem> </mx:Form> </mx:Panel>
LoginPanel组件包含有一个用用户表单输入新创建的LoginVO对象,当用户单击“Login”按钮时触发一个事件,接下来的事情由LoginPanelMediator接管。
这样View Component的角色就是简单收集数据,收集完数据通知系统。
可以完善的地方是只有当username和password都有内容时才让login按钮可用(enable),这样可以避免恶意登录。
View Component对外隐藏自己的内部实现,它由Mediator使用的整个API包括:一个TRY_LOGIN事件,一个LoginVO属性和Panel的状态属性。
LoginPanelMediator会对LOGIN_FAILED和LOGIN_SUCCESS通知做出反应,设置LoginPanel的状态。
LoginPanelMediator.as:
package com.me.myapp.view { import flash.events.Event; import org.puremvc.as3.interfaces.*; import org.puremvc.as3.patterns.mediator.Mediator; import com.me.myapp.model.LoginProxy; import com.me.myapp.model.vo.LoginVO; import com.me.myapp.ApplicationFacade; import com.me.myapp.view.components.LoginPanel; // LoginPanel视图的Mediator public class LoginPanelMediator extends Mediator implements IMediator { public static const NAME:String = 'LoginPanelMediator'; public function LoginPanelMediator( viewComponent:LoginPanel ) { super( NAME, viewComponent ); LoginPanel.addEventListener( LoginPanel.TRY_LOGIN, onTryLogin ); } // 列出该Mediator关心的Notification override public function listNotificationInterests( ) : Array { return [ LoginProxy.LOGIN_FAILED, LoginProxy.LOGIN_SUCCESS ]; } // 处理Notification override public function handleNotification( note:INotification ):void { switch ( note.getName() ) { case LoginProxy.LOGIN_FAILED: LoginPanel.loginVO = new LoginVO( ); loginPanel.loginStatus = LoginPanel.NOT_LOGGED_IN; break; case LoginProxy.LOGIN_SUCCESS: loginPanel.loginStatus = LoginPanel.LOGGED_IN; break; } } // 户单击用Login钮尝试录按,登。 private function onTryLogin ( event:Event ) : void { sendNotification( ApplicationFacade.LOGIN, loginPanel.loginVO ); } // 把viewComponent转类化成它真正的型。 protected function get loginPanel() : LoginPanel { return viewComponent as LoginPanel; } } }
注意LoginPanelMediator在构造方法中给LoginPanel注册了一个侦听方法——onTryLogin,当用户单击Login按钮时这个方法会被执行。在onTryLogin方法里发送了一个LOGIN的Notification(通知,携带参数LoginVO对象)。
早先(在ApplicationFacade中)我们已经把LoginCommand注册到这个Notification上了。LoginCommand会调用LoginProxy的“登录”方法,传参LoginVO。LoginProxy把“登录”请求远程服务,之后发送LOGIN_SUCCESS(登录成功)或LOGIN_FAILED(登录失败)的Notification。这些类的定义请参见“Proxy”章节。
LoginPanelMediator把LOGIN_SUCCESS和LOGIN_FAILED注册自己的关心的Notification,当这两个Notification被发送时,Mediaotr作为响应把LoginPanel的loginStatus设置为LOGGED_IN(登录成功时)或NOT_LOGGED_IN(登录失败时),并清除LoginVO对象。
LoginCommand.as
package com.me.myapp.controller { import org.puremvc.as3.interfaces.*; import org.puremvc.as3.patterns.command.*; import com.me.myapp.model.LoginProxy; import com.me.myapp.model.vo.LoginVO; public class LoginCommand extends SimpleCommand { override public function execute( note: INotification ) : void { var loginVO : LoginVO = note.getBody() as LoginVO; var loginProxy: LoginProxy; loginProxy = facade.retrieveProxy( LoginProxy.NAME ) as LoginProxy; loginProxy.login( loginVO ); } } }
LoginProxy.as
package com.me.myapp.model { import mx.rpc.events.FaultEvent; import mx.rpc.events.ResultEvent; import mx.rpc.remoting.RemoteObject; import org.puremvc.as3.interfaces.*; import org.puremvc.as3.patterns.proxy.Proxy; import com.me.myapp.model.vo.LoginVO; // 用于用户登录的Proxy public class LoginProxy extends Proxy implements IProxy { public static const NAME:String = 'LoginProxy'; public static const LOGIN_SUCCESS:String = 'loginSuccess'; public static const LOGIN_FAILED:String = 'loginFailed'; public static const LOGGED_OUT:String = 'loggedOut'; private var loginService: RemoteObject; public function LoginProxy () { super( NAME, new LoginVO ( ) ); loginService = new RemoteObject(); loginService.source = "LoginService"; loginService.destination = "GenericDestination"; loginService.addEventListener( FaultEvent.FAULT, onFault ); loginService.login.addEventListener( ResultEvent.RESULT, onResult ); } // 隐式getter,转化data的类型 public function get loginVO( ) : LoginVO { return data as LoginVO; } //如果logivVO中包含了authToken(授权标识)表示用户登录成功 public function get loggedIn():Boolean { return ( authToken != null ); } // 取得authToken public function get authToken():String { return loginVO.authToken; } //置用的限设户权标识,登录,退出,或登继续尝试录。 public login( tryLogin:LoginVO ) : void { if ( ! loggedIn ) { loginVO.username= tryLogin.username; loginVO.password = tryLogin.password; } else { logout(); login( tryLogin ); } } // 退出,地清空简单LoginVO public function logout( ) : void { if ( loggedIn ) loginVO = new LoginVO( ); sendNotification( LOGGED_OUT ); } //通知系登成功统录 private function onResult( event:ResultEvent ) : void { setData( event.result ); // immediately available as loginVO sendNotification( LOGIN_SUCCESS, authToken ); } //通知系登失统录败 private function onFault( event:FaultEvent) : void { sendNotification( LOGIN_FAILED, event.fault.faultString ); } } }
一个LoginCommand会获取LoginProxy,设置登录的数据,调用登录函数,呼叫登录服务。
接下来,可能一个GetPrefsCommand会响应LOGIN_SUCCESS(登录成功)这个Notification,从Notificaiton的“报体”中获取authToken(授权标识),接着呼叫下一个服务,获取用户的(比如)配置信息(preferences)。
GetPrefsCommand.as
package com.me.myapp.controller { import org.puremvc.as3.interfaces.*; import org.puremvc.as3.patterns.command.*; import com.me.myapp.model.LoginProxy; import com.me.myapp.model.vo.LoginVO; public class GetPrefsCommand extends SimpleCommand { override public function execute( note: INotification ) : void { var authToken : String = note.getBody() as String; var prefsProxy : PrefsProxy; prefsProxy = facade.retrieveProxy( PrefsProxy.NAME ) as PrefsProxy; prefsProxy.getPrefs( authToken ); } } }
由于此次给出的感性认识在于pureMVC的处理流程,因此,关于整个流程流转的配置略,若您有兴趣或者期望更进一步了解pureMVC,了解Mediator、Command、Proxy各自的职责等,可参见附件的那份中文文档或访问官网。