Flex 无疑是RIA第一位得选择,而JAVA 可以是Enterprise application 的第一选择。 结合二者来开发Web App 无疑是一种走向流行的方案。 前端Flex+后端JAVA的简单架构如图:
在实际得开发中实现上述结构的方法非常之多。我只是将自己开发的渐变过程记录下来。希望对后来者有所借鉴。
这个系列blog包括:
一。混沌未开-Flex-all-in-one
二。中央管理-Flex Central Managerment
三。MVC框架-Flex Cairngorm
四。咔嚓Front Controller的Cairngorm
五。轮回转世-Mate
样例。
为简洁起见,用一个非常简单的样例来演示开发过程:密友列表
系统只有二个画面:
登录-LoginView:
输入用户名和密码并登录, 进入密友列表画面(BuddyListView):
由于这个系列blog焦点在Flex应用开发方式,所以只选择Remote Object 作为和后端通讯的方式。AMF的实现使用Adobe 的opensource data service-BlazeDS。Java代码非常简单。仅限于配合这个blog系列。
相关得配置文件和JAVA class 如下:
1。remot-config.xml:
- <?xml version=”1.0″ encoding=”UTF-8″?>
- <service id=”remoting-service”
- class=”flex.messaging.services.RemotingService”>
- <adapters>
- <adapter-definition id=”java-object” class=”flex.messaging.services.remoting.adapters.JavaAdapter” default=”true”/>
- </adapters>
- <default-channels>
- <channel ref=”my-amf”/>
- </default-channels>
- <destination id=”flexmvcRO”>
- <properties>
- <source>com.ny.blog.flex.mvc.accessor.DummyAccessor</source>
- <scope>session</scope>
- </properties>
- <adapter ref=”java-object” />
- </destination>
- </service>
2.DummyAccessor.java:
- package com.ny.blog.flex.mvc.accessor;
- import java.util.ArrayList;
- import java.util.List;
- import com.ny.blog.flex.mvc.pojo.Friend;
- public class DummyAccessor {
- public DummyAccessor() {
- }
- public boolean login(String userName,String password){
- return true;
- }
- public List<Friend> getAllFriends(String userName){
- List<Friend> myBuddy = new ArrayList<Friend>();
- Friend dummy1 = new Friend();
- dummy1.setFirstName(”John”);
- dummy1.setLastName(”Smith”);
- myBuddy.add(dummy1);
- Friend dummy2 = new Friend();
- dummy2.setFirstName(”Andy”);
- dummy2.setLastName(”Jones”);
- myBuddy.add(dummy2);
- Friend dummy3 = new Friend();
- dummy3.setFirstName(”Michael”);
- dummy3.setLastName(”Niu”);
- myBuddy.add(dummy3);
- return myBuddy;
- }
package com.ny.blog.flex.mvc.accessor;
import java.util.ArrayList;
import java.util.List;
import com.ny.blog.flex.mvc.pojo.Friend;
public class DummyAccessor {
public DummyAccessor() {
}
public boolean login(String userName,String password){
return true;
}
public List<Friend> getAllFriends(String userName){
List<Friend> myBuddy = new ArrayList<Friend>();
Friend dummy1 = new Friend();
dummy1.setFirstName(”John”);
dummy1.setLastName(”Smith”);
myBuddy.add(dummy1);
Friend dummy2 = new Friend();
dummy2.setFirstName(”Andy”);
dummy2.setLastName(”Jones”);
myBuddy.add(dummy2);
Friend dummy3 = new Friend();
dummy3.setFirstName(”Michael”);
dummy3.setLastName(”Niu”);
myBuddy.add(dummy3);
return myBuddy;
}
3.相关 pojo Friend.java:
- public class Friend {
- private String firstName;
- private String lastName;
- private String nickName;
- public Friend() {
- }
- //getter and setters
- …
- }
public class Friend {
private String firstName;
private String lastName;
private String nickName;
public Friend() {
}
//getter and setters
…
}
Flex 开发架构(一): 混沌未开-Flex-all-in-one
混沌未开,顾名思义就是匹萨店里面烘烤的色香味俱全的一个pizza大饼,在一个面饼的底板上,混合所有的原料。 Lets make Pizza!
Flex与生俱来的是它的事件驱动(event-driven)的特点。就是说,Flex可以使用它的标签做到任何事情。因此开发一个Flex应用程序最简单,最基础的方法就是使用Flex标签,先来看看代码。
Loginview,用户界面部分:
- <mx:Form id=”loginForm” x=”0″ y=”0″>
- <mx:FormItem label=”Username:” >
- <mx:TextInput id=”username” />
- </mx:FormItem>
- <mx:FormItem label=”Password:” >
- <mx:TextInput id=”password” displayAsPassword=”true” />
- </mx:FormItem>
- <mx:FormItem direction=”horizontal” verticalGap=”15″ paddingTop=”5″ width=”170″>
- <mx:Button id=”loginBtn” label=”Login” click=”login()”/>
- </mx:FormItem>
- </mx:Form>
使用 <mx:RemoteObject> 标签调用远程服务:
- <mx:RemoteObject id=”loginReq” destination=”flexmvcRO”>
- <mx:method name=”login” result=”loginHandler(event)” fault=”mx.controls.Alert.show(event.fault.faultString)”>
- <mx:arguments>
- <userName>{username.text}</userName>
- <password>{password.text}</password>
- </mx:arguments>
- </mx:method>
- </mx:RemoteObject>
现在,在login方法中发送请求:
- if(Validator.validateAll(validators).length == 0){
- loginReq.login.send();
- }
在发送请求之后,需要建立一个返回结果的处理方法:
- private function loginHandler(event:ResultEvent):void{
- var isLogin:Boolean = event.result as Boolean;
- if(isLogin){
- this.parentApplication.viewStack.selectedIndex=1;
- dispatchEvent(new LoginUserEvent(username.text));
- }
- }
最后,在页面之间建立联系,在这里我使用播送事件:
- dispatchEvent(new LoginUserEvent(username.text));
播送用户自定义的事件,就必须在代码最前面写入下述的元标签代码:
- <mx:Metadata>
- [Event(name="loginUser", type="flash.events.Event")]
- </mx:Metadata
然后,建立用户自定义的事件LoginUserEvent.as:
- import flash.events.Event;
- public class LoginUserEvent extends Event
- {
- public static const LOGINUSEREVENT:String =”loginUser”;
- public var loginUserName:String = “”;
- public function LoginUserEvent(userName:String)
- {
- super(LOGINUSEREVENT, true, true);
- this.loginUserName = userName;
- }
- override public function clone():Event {
- return new LoginUserEvent(loginUserName);
- }
当登录成功后,密友列表页面将会显示,BuddyListVew.mxml:
重要的是在列表代码中,首先要监听LoginUserEvent,因此要创建一个preinitialize的方法:
- <mx:Panel xmlns:mx=”http://www.adobe.com/2006/mxml” title=”Buddy List of {loginUserName}” preinitialize=”init()” width=”500″ height=”320″>
- private function init():void{
- this.parentApplication.addEventListener(LoginUserEvent.LOGINUSEREVENT, getUserName);
- }
然后完成事件监听的管理程序:
- private function getUserName(event:LoginUserEvent):void{
- loginUserName = event.loginUserName;
- loginReq.getAllFriends.send();
- }
发送获得全部密友列表的请求,并且创建对应的管理方法:
- private function getAllFriendsHandler(event:ResultEvent):void{
- mybuddyList = event.result as ArrayCollection;
- }
在代码中,必须包含定义远程对象的标签:
- <mx:RemoteObject id=”loginReq” destination=”flexmvcRO”>
- <mx:method name=”getAllFriends” result=”getAllFriendsHandler(event)” fault=”mx.controls.Alert.show(event.fault.faultString)”>
- <mx:arguments>
- <userName>{loginUserName}</userName>
- </mx:arguments>
- </mx:method>
- </mx:RemoteObject>
使用Flex标签完成程序方法非常简单,并且对于简单的系统来说, 也是非常有效,其商务逻辑层也不复杂。但在实际的应用中,并非只存在这样的项目,相反,实际工作中往往需要大量的远程对象的通讯。
好了,来看看:中央管理-Flex Central Management.
Flex Chaos-All-in-one这一节中所提到的,在大型项目中,将所有的代码放在一起并非明智之举,确切的讲:正确的方法是将商业逻辑层与UI层分离开来。
中央管理的理念是使用一个远程对象管理器来控制Flex与后端的通讯。其构建体系如下图所示
图中每一个UI组件都将调用一个服务(Service),服务类将调用中央管理器(Central Manager),中央管理器类将调用服务器端的解决方案。而图中全局对象管理器(Global Object Manager)将用来在UI之间传递数据。
现在来看看简单密友列表应用的实现。
首先是LoginView.xml
- <?xml version=”1.0″ encoding=”utf-8″?>
- <mx:Panel xmlns:mx=”http://www.adobe.com/2006/mxml” layout=”absolute” width=”300″ height=”200″ horizontalAlign=”center” verticalAlign=”middle” title=”Flex Central Manager Login”>
- <mx:Script>
- <![CDATA[
- import com.ny.flex.centralManagement.service.LoginService;
- import mx.validators.Validator;
- import mx.containers.ViewStack;
- import mx.rpc.events.ResultEvent;
- private function login():void{
- if(Validator.validateAll(validators).length == 0){
- LoginService.getInstance().login(username.text,password.text);
- }
- }
- ]]>
- </mx:Script>
- <!– Validators–>
- <mx:Array id=”validators”>
- <mx:StringValidator id=”userNameValidator” source=”{username}” property=”text” required=”true”/>
- <mx:StringValidator id=”passwordValidator” source=”{password}” property=”text” required=”true” />
- </mx:Array>
- <mx:Form id=”loginForm” x=”0″ y=”0″>
- <mx:FormItem label=”Username:” >
- <mx:TextInput id=”username” />
- </mx:FormItem>
- <mx:FormItem label=”Password:” >
- <mx:TextInput id=”password” displayAsPassword=”true” />
- </mx:FormItem>
- <mx:FormItem direction=”horizontal” verticalGap=”15″ paddingTop=”5″ width=”170″>
- <mx:Button id=”loginBtn” label=”Login” click=”login()”/>
- </mx:FormItem>
- </mx:Form>
- </mx:Panel>
其功能的核心是:
LoginService.getInstance().login(username.text,password.text);
它的作用是有效的分离了商务逻辑层和视图组件。在此服务类程序不需支持任何状态,因此我们保持其单件模式(singleton)。
LoginService类文件如下:
- import com.ny.flex.centralManagement.event.DataManagerResultEvent;
- import com.ny.flex.centralManagement.manager.GlobalObjectManager;
- import com.ny.flex.centralManagement.manager.RemoteObjectManager;
- public class LoginService
- {
- public var roManager:RemoteObjectManager = null;
- public var gom:GlobalObjectManager = GlobalObjectManager.getInstance();
- private static var _instance:LoginService =null;
- public static function getInstance():LoginService{
- if(_instance == null){
- _instance = new LoginService(new PrivateClass)
- }
- return _instance;
- }
- public function LoginService(privateclass:PrivateClass)
- {
- if(LoginService._instance == null){
- LoginService._instance = this;
- }
- }
- public function login(userName:String,password:String):void{
- roManager = RemoteObjectManager.getRemoteObjectManager(”flexmvcRO”);
- roManager.addEventListener(”getLoginUser”,loginHandler);
- var params:Array = new Array(userName,password);
- roManager.makeRemoteCall(”getLoginUserName”,”getLoginUser”,params);
- }
- private function loginHandler(event:DataManagerResultEvent):void {
- var userName:String = event.result as String;
- if(userName){
- gom.loginUserName = userName;
- gom.viewStackSelectedIndex=1;
- }
- }
- }
代码中有两个特别的对象:
RemoteObjectManager
GlobalObjectManager
RemoteObjectManager是一个单件的类,用来实现中央管理所有的远程对象通讯。原始的代码来自于Jeff Tapper的博客:
Creating a Remote Object DataManager in ActionScript 3.0 for Flex 2.0
RemoteObjectManager.as:
- package com.ny.flex.centralManagement.manager
- {
- import com.ny.flex.centralManagement.event.DataManagerResultEvent;
- import flash.events.EventDispatcher;
- import mx.core.Application;
- import mx.resources.ResourceManager;
- import mx.rpc.AbstractOperation;
- import mx.rpc.AsyncToken;
- import mx.rpc.events.FaultEvent;
- import mx.rpc.events.ResultEvent;
- import mx.rpc.remoting.mxml.RemoteObject;
- public class RemoteObjectManager extends EventDispatcher {
- public var ro:RemoteObject;
- private var eventName:String;
- private static var instanceMap:Object = new Object();
- public function RemoteObjectManager(pri:PrivateClass,dest:String){
- this.ro = new RemoteObject();
- ro.destination = dest;
- }
- public static function getRemoteObjectManager(dest:String):RemoteObjectManager{
- if(RemoteObjectManager.instanceMap[dest] == null){
- RemoteObjectManager.instanceMap[dest] = new RemoteObjectManager(new PrivateClass(),dest);
- }
- var dm:RemoteObjectManager= RemoteObjectManager.instanceMap[dest];
- return dm;
- }
- public function makeRemoteCall(methodName:String,eventName:String,args:Array=null):void{
- this.eventName = eventName;
- var op:mx.rpc.AbstractOperation = ro[methodName];
- ro.addEventListener("result", doResults);
- ro.addEventListener("fault", doFault);
- var token:AsyncToken = null;
- if(args && args.length >0){
- token = op.send.apply(null,args);
- }
- else {
- token = op.send();
- }
- token.eventName = eventName;
- }
- private function doResults(event:ResultEvent):void{
- var e:DataManagerResultEvent = new DataManagerResultEvent(event.token.eventName, event.result);
- this.dispatchEvent(e);
- }
- private function doFault(fault:FaultEvent):void{
- this.dispatchEvent(fault);
- }
- public override function toString():String{
- return "RemoteObjectDataManager";
- }
- }
- }
- /** PrivateClass is used to make DataManager constructor private */
- class PrivateClass{
- public function PrivateClass() {}
- }
“GlobalObjectManager”用来在UI之间传递信息,例如,我们使用ViewStack的selectedIndex来决定显示ViewStack中的哪一个视图,则使用全局对象viewStackSelectedIndex ,其代码如下面的黑体部分:
- <?xml version=”1.0″ encoding=”utf-8″?>
- <mx:Application xmlns:mx=”http://www.adobe.com/2006/mxml“ xmlns:views=”com.ny.flex.centralManagement.views.*” layout=”absolute” width=”100%” height=”100%”>
- <mx:Script>
- <![CDATA[
- import mx.binding.utils.BindingUtils;
- import com.ny.flex.centralManagement.manager.GlobalObjectManager;
- [Bindable]
- public var gom:GlobalObjectManager=GlobalObjectManager.getInstance();
- ]]>
- </mx:Script>
- <mx:HBox horizontalAlign=”center” verticalAlign=”top” width=”100%” height=”100%” y=”0″ x=”0″>
- <mx:ViewStack id=”viewStack” resizeToContent=”true” selectedIndex=”{gom.viewStackSelectedIndex}” >
- <views:LoginView />
- <views:BuddyListView/>
- </mx:ViewStack>
- </mx:HBox>
- </mx:Application>
再回头看看在
LoginService 代码中的loginHandler方法,在此viewStackSelectedIndex 全局对象被更新。
LoginService
代码中的loginHandler方法,在此viewStackSelectedIndex 全局对象被更新。
- private function loginHandler(event:DataManagerResultEvent):void {
- var userName:String = event.result as String;
- if(userName){
- gom.loginUserName = userName;
- gom.viewStackSelectedIndex=1;
- }
- }
[Bindable]元标签使得其值的改变立刻生效。
BuddyList.mxml代码:
- <?xml version=”1.0″ encoding=”utf-8″?>
- <mx:Panel xmlns:mx=”http://www.adobe.com/2006/mxml” title=”Buddy List of {gom.loginUserName}” creationComplete=”init()” width=”500″ height=”320″>
- <mx:Script>
- <![CDATA[
- import com.ny.flex.centralManagement.service.BuddyService;
- import com.ny.flex.centralManagement.manager.GlobalObjectManager;
- import mx.collections.ArrayCollection;
- import mx.rpc.events.ResultEvent;
- [Bindable]
- public var gom:GlobalObjectManager = GlobalObjectManager.getInstance();
- private function init():void{
- BuddyService.getInstance().getBuddyList();
- }
- ]]>
- </mx:Script>
- <mx:DataGrid id=”buddyList” dataProvider=”{gom.mybuddyList}” borderStyle=”none” width=”100%” height=”100%” >
- <mx:columns>
- <mx:DataGridColumn dataField=”firstName” headerText=”First Name”/>
- <mx:DataGridColumn dataField=”lastName” headerText=”Last Name”/>
- </mx:columns>
- </mx:DataGrid>
- </mx:Panel>
BuddyService是另一个服务类代码用来和远程对象通讯。
这是最符合本人喜好的Flex程序的框架结构。它的优点是非常的清晰,没有累赘的发送和监听事件的工作,并且代码非常容易维护。遗憾的是,在此还没有获得足够的理论支持这一框架理论。
再来看看MVC框架的代表:MVC-Cairngorm。
Flex 开发架构(二): 中央管理-Flex Central Managerment
有没有听说过这个奇怪的词汇:“Cairngorm”?如果你的回答是“No” && 你是Flex程序员,哪你就看看自己是不是住在一个井底。J
Cairngorm是Flex的一个MVC框架结构,名字取自苏格兰的一个山脉。(kao,如果是我建立一个自己的框架结构,我就取名叫:“天安门”。)
有关这个框架,在网络上有很多图表用来讨论。下面是我所理解的框架图表:
使用Cairngorm的第一步是建立框架结构的骨架,包括了三个对象:
Model Locater;
Service Locator;
Front Controller;
Model Locator:承载了组件之间的所有的传递的信息和数据,这是一个Bindable(可绑定的)对象。
Service Locator:定义了与数据源(Httpservice,Webservice,Remoteobject)之间通讯的界面。
Front Controller:建立播送事件(Dispatch event)和命令层(command)之间的对应关系(mapping)。
看一下相关的代码:
BuddyAppModelLocator.as:
- <em>package com.ny.flex.cairngorm.model
- {
- import com.ny.flex.cairngorm.vo.User;
- import mx.collections.ArrayCollection;
- [Bindable]
- public class BuddyAppModelLocator
- {
- public var buddyList:ArrayCollection=new ArrayCollection();
- public var loginUser:User=new User();
- public var viewStackSelectedIndex :int = 0;
- static private var __instance:BuddyAppModelLocator=null;
- static public function getInstance():BuddyAppModelLocator
- {
- if(__instance == null)
- {
- __instance=new BuddyAppModelLocator();
- }
- return __instance;
- }
- }
- }
- </em>
在Model Locator代码中,定义了三个public的变量,buddyList:用来存放由数据库获取的密友列表;loginUser:定义一个User类型对象;viewStackSelectedIndex:定义viewStack指向的视窗。
几乎所有的服务层返回的信息都需要在Model Locator中有一个相应的对象。
BuddyServiceLocator.mxml:
- <em><em><?xml version=”1.0″ encoding=”utf-8″?>
- <cairngorm:ServiceLocator xmlns:mx=”http://www.adobe.com/2006/mxml” xmlns:cairngorm=”http://www.adobe.com/2006/cairngorm“>
- <mx:RemoteObject id=”buddyRo“ destination=”flexmvcRO” >
- </mx:RemoteObject>
- </cairngorm:ServiceLocator>
- </em></em>
上述代码定义了程序将要调用的RemoteObject ,RemoteObject 所调用的Destination需要和remote_config.xml文件中的Destination相一致。在此,Destination的值为“flexmvcRO”。
BuddyListController.as:
- <em><em><strong>package com.ny.flex.cairngorm.control
- {
- import com.adobe.cairngorm.control.FrontController;
- import com.ny.flex.cairngorm.command.GetBuddyListCommand;
- import com.ny.flex.cairngorm.command.LoginCommand;
- import com.ny.flex.cairngorm.event.GetBuddyListEvent;
- import com.ny.flex.cairngorm.event.LoginEvent;
- public class BuddyListController extends FrontController
- {
- public function BuddyListController()
- {
- super();
- addCommand(LoginEvent.LOGIN_EVENT,LoginCommand);
- addCommand(GetBuddyListEvent.GET_BUDDY_LIST_EVENT,
- GetBuddyListCommand);
- }
- }
- }
- </strong></em></em>
很显然,上述的Controller代码是事件和命令的对应处理的地方。
如何能将这些乱七八糟的东西结合在一起?其Magic的地方是在主页(Main application)上,代码如下:
BuddList_Main_Cairngorm.mxml:
- <em><em><strong><?xml version=”1.0″ encoding=”utf-8″?>
- <mx:Application xmlns:mx=”http://www.adobe.com/2006/mxml“ xmlns:service=”com.ny.flex.cairngorm.service.*“ xmlns:controller=”com.ny.flex.cairngorm.control.*” xmlns:views=”com.ny.flex.cairngorm.views.*” layout=”absolute“ width=”100%” height=”100%“>
- <mx:Script>
- <![CDATA[
- import com.ny.flex.cairngorm.model.BuddyAppModelLocator;
- [Bindable]
- public var myModel:BuddyAppModelLocator = BuddyAppModelLocator.getInstance();
- ]]>
- </mx:Script>
- <service:BuddyServiceLocator id=”myservice“/>
- <controller:BuddyListController id=”myController“/>
- <mx:HBox horizontalAlign=”center” verticalAlign=”top“ width=”100%” height=”100%” y=”0” x=”0“>
- <mx:ViewStack id=”viewStack“ resizeToContent=”true” selectedIndex=”{myModel.viewStackSelectedIndex}” >
- <views:LoginView />
- <views:BuddyListView/>
- </mx:ViewStack>
- </mx:HBox>
- </mx:Application>
- </strong></em></em>
现在用户可以建立视图组件,并从这些组件中播送事件:
LoginView.mxml:
- <em><em><strong><em> <![CDATA[
- import com.ny.flex.cairngorm.event.LoginEvent;
- import com.ny.flex.cairngorm.vo.User;
- import mx.validators.Validator;
- private function login():void{
- if(Validator.validateAll(validators).length == 0){
- var loginUser:User = new User();
- loginUser.userName=username.text;
- loginUser.password=password.text;
- var loginEvent:LoginEvent = new LoginEvent();
- loginEvent.loginUser = loginUser;
- loginEvent.dispatch();
- }
- }
- ]]>
- </mx:Script>
- <!– Validators–>
- <mx:Array id=”validators“>
- <mx:StringValidator id=”userNameValidator” source=”{username}“ property=”text“ required=”true“/>
- <mx:StringValidator id=”passwordValidator” source=”{password}“ property=”text” required=”true” />
- </mx:Array>
- <mx:Form id=”loginForm” x=”0” y=”0“>
- <mx:FormItem label=”Username:” >
- <mx:TextInput id=”username” />
- </mx:FormItem>
- <mx:FormItem label=”Password:” >
- <mx:TextInput id=”password” displayAsPassword=”true” />
- </mx:FormItem>
- <mx:FormItem direction=”horizontal” verticalGap=”15” paddingTop=”5” width=”170“>
- <mx:Button id=”loginBtn” label=”Login” click=”login()”/>
- </mx:FormItem>
- </mx:Form>
- </mx:Panel>
- </em></strong></em></em>
每一个动作都需要建立一个相应的事件:
LoginEvent.as:
- <em><em><strong><em>package com.ny.flex.cairngorm.event
- {
- import com.adobe.cairngorm.control.CairngormEvent;
- import com.ny.flex.cairngorm.vo.User;
- import flash.events.Event;
- public class LoginEvent extends CairngormEvent
- {
- public static var LOGIN_EVENT:String = “loginEvent”
- public var loginUser:User ;
- public function LoginEvent()
- {
- super(LOGIN_EVENT);
- }
- override public function clone() : Event
- {
- return new LoginEvent();
- }
- }
- }
- </em></strong></em></em>
每一个事件都 要对应于一个命令:
LoginCommand.as:
- <em><em><strong>package com.ny.flex.cairngorm.command
- {
- import com.adobe.cairngorm.commands.ICommand;
- import com.adobe.cairngorm.control.CairngormEvent;
- import com.ny.flex.cairngorm.event.LoginEvent;
- import com.ny.flex.cairngorm.model.BuddyAppModelLocator;
- import com.ny.flex.cairngorm.service.LoginDelegate;
- import com.ny.flex.cairngorm.vo.User;
- import mx.controls.Alert;
- import mx.rpc.IResponder;
- public class LoginCommand implements ICommand, IResponder
- {
- public function LoginCommand()
- {
- }
- public function execute(event:CairngormEvent):void
- {
- var loginEvent:LoginEvent = LoginEvent(event);
- var user:User = loginEvent.loginUser;
- var lgoinService :LoginDelegate
- = new LoginDelegate(this);
- lgoinService.authenticate(user);
- }
- public function result(event:Object):void
- {
- var authUser:User = User(event.result);
- BuddyAppModelLocator.getInstance().loginUser = authUser;
- BuddyAppModelLocator.getInstance().viewStackSelectedIndex=1;
- }
- public function fault(info:Object):void
- {
- Alert.show(“Login Fail Error “);
- }
- }
- }
- </strong></em></em>
然后,在Front Controller(前端控制器)中build对应关系:
addCommand(LoginEvent.LOGIN_EVENT,LoginCommand);
命令层需要完成商务逻辑,用户需要在执行方法中加入商务逻辑代码:
- <em><em><strong> var lgoinService :LoginDelegate =
- new LoginDelegate(this);
- lgoinService.authenticate(user);
- </strong></em></em>
Delegate(代表)用来通过服务层(Service Locator)调用数据源:
LoginDelegate.as:
- <em><em><strong><em>package com.ny.flex.cairngorm.service
- {
- import com.adobe.cairngorm.business.ServiceLocator;
- import com.ny.flex.cairngorm.vo.User;
- import mx.rpc.IResponder;
- public class LoginDelegate
- {
- private var responder:IResponder;
- private var service:Object;
- public function LoginDelegate(responder :IResponder){
- this.service =
- ServiceLocator.getInstance()
- .getRemoteObject(“buddyRo”);
- this.responder = responder;
- }
- public function authenticate(user:User):void{
- var call:Object = service.authenticate(user);
- call.addResponder(responder);
- }
- }
- }
- </em></strong></em></em>
返回的结果将回复到命令层(LoginCommand.as)的结果方法中,在此方法中Model被更新,然后数据被绑定到结果视图上:
LoginCommand.as:
- <em><em><strong> public function result(event:Object):void
- {
- var authUser:User = User(event.result);
- BuddyAppModelLocator.getInstance().loginUser
- = authUser;
- BuddyAppModelLocator.getInstance().viewStackSelectedIndex=1;
- }
- </strong></em></em>
其它的视图工作流程同上,整个密友列表项目的结构如下图所示:
使用Cairngorm开发应用项目Layer,测试性高。并且使得程序员更专业化。
但这个框架的确很不容易学习和维护,那么有没有更好的方法简化它?
来看看:咔嚓Front Controller的Cairngorm。
Flex 开发架构(四): 去除FrontController 的Cairngorm
正如在使用Cairngorm时,视图中的每一个动作都播送一个事件,每个播送出去的事件都需要建立相应的命令代码来处理事件。并且需要在FrontController中对应他们的关系。例如下面的简单流程:
Loginvew.xml —> action login() —>dispatch LoginEvent —>Handled by LoginCommand —>mapping LoginEvent and LoginCommand in FrontController.
在不考虑商务层代码的情况下,当用户建立然后新的动作时,就需要建立2个新的代码,修改Controller代码。
那么如何简化Cairngorm,我的方法是去掉FrontController,取而代之的是Service Façade,其框架图如下所示:
在上图中,视图不再播送如何事件,而是直接调用Service Façade。而Service Façade则直接调用商务委托层与服务器端通讯(如Remote Object等),然后商务委托处理结果并更新Model Locator,最后Model Locator通过绑定(Binding)更新视窗中的结果。
看一下被改变的代码:
ServiceFacade.as:
- package com.ny.flex.cairngorm.no_fc.service
- {
- import com.ny.flex.cairngorm.no_fc.vo.User;
- public class ServiceFacade
- {
- private static var _serviceFacade:ServiceFacade = null;
- public function ServiceFacade(privateClass:PrivateClass)
- {
- if(ServiceFacade._serviceFacade == null){
- ServiceFacade._serviceFacade = this;
- }
- }
- public static function getInstance():ServiceFacade {
- if(_serviceFacade == null){
- _serviceFacade = new ServiceFacade(new PrivateClass);
- }
- return _serviceFacade;
- }
- public function authenticate(user:User):void{
- LoginDelegate.getInstance().authenticate(user);
- }
- public function getBuddyList():void{
- BuddyListDelegate.getInstance().getBuddyList();
- }
- }
- }
- class PrivateClass{}
ServiceFacade提供了对所有商务逻辑为一体的界面,视窗的行为(Action)仅仅调用façade,例如下面代码中的Login的动作。
Loginvew.xml :
- private function login():void{
- if(Validator.validateAll(validators).length == 0){
- var loginUser:User = new User();
- loginUser.userName=username.text;
- loginUser.password=password.text;
- <span style="color: rgb(255, 102, 0);"><strong><em>serviceFacade.authenticate(loginUser);</em></strong></span>
- }
- }
函数 serviceFacade.authenticate(loginUser)如下:
- <strong> public function authenticate(user:User):void{
- <span style="color: rgb(255, 102, 0);"><strong><em>LoginDelegate.</em></strong></span>getInstance().authenticate(user);
- }
- </strong>
ServiceFacade使用LoginDelegate 来真正实现Business Logic:
LoginDelegate.as:
- <strong>package com.ny.flex.cairngorm.no_fc.service
- {
- import com.ny.flex.cairngorm.no_fc.*;
- import com.ny.flex.cairngorm.no_fc.vo.User;
- import mx.rpc.IResponder;
- import mx.rpc.Responder;
- import mx.rpc.events.ResultEvent;
- public class LoginDelegate extends BaseDelegate
- {
- private static var _loginDelegate:LoginDelegate = null;
- public function LoginDelegate(privateClass:PrivateClass){
- if(LoginDelegate._loginDelegate == null ){
- LoginDelegate._loginDelegate = this;
- }
- }
- public static function getInstance():LoginDelegate{
- if(_loginDelegate == null){
- _loginDelegate = new LoginDelegate(new PrivateClass);
- }
- return _loginDelegate;
- }
- public function <span style="color: rgb(255, 102, 0);"><strong><em> authenticate</em></strong></span>(user:User):void{
- var responder:IResponder = new Responder(onResult_Authenticate,fault);
- var call:Object = service.authenticate(user);
- call.addResponder(responder);
- }
- private function onResult_Authenticate(event:ResultEvent):void{
- var authUser:User = event.result as User;
- model.loginUser = authUser;
- model.viewStackSelectedIndex = 1;
- }
- }
- }
- class PrivateClass{}</strong>
上面的authenticate(user) 实现于后台通讯并处理结果,更新Model Locator。
其他的视图和相应的处理方法 与上面类似。
这个解决方案简化了Cairngorm的框架,使得代码更易于理解。而且,彻底摆脱了 无味的dispatcher,frontcontroller 和command。 编码效率会提高 并且易于调试。
但其弱点是显而易见的:它违犯了解耦的(就是使得设计程序耦合性尽可能的降低)设计规范,使得界面上的行为(Action)和Serivce Façade产生了耦合关系。
在最后一篇中我将讨论下一个Flex 开发的热点:Mate-标签化的框架。
Flex 开发架构(五): Mate-基于标签的框架
Mate 将会成为Flex领域的另一个热点。它使用设置(configuration)来调用Service,处理结果,同样也使用设置文件来更新绑定对象(Bindable object)。从某个角度来说:Mate是Flex领域的“springframework”。
Mate有两个架构层面的图表。一个是来自Yakov Fain of Farata Systems,另一个来自ASFusion。我更喜欢后者,其构架图如下:
http://mate.asfusion.com/assets/content/diagrams/two_way_view_injection.png
来看看用Mate的编程方式来建立buddyList应用程序。
1,建立Mate的核心组件:EventMap。
EventMap是Mate的心脏,它黏着了所有的组件和控件。其代码如下:
BuddyListEventMap.mxml:
- <?xml version=”1.0″ encoding=”utf-8″?>
- <EventMap xmlns=”http://mate.asfusion.com/”
- xmlns:mx=http://www.adobe.com/2006/mxml>
- </EventMap>
其它内容稍后再填写,现在我们需要告诉主程序(Main Application)初始化EventMap:
主程序 Flex_Mate.mxml:
- <?xml version="1.0" encoding="utf-8"?>
- <mx:Application xmlns:map="com.ny.flex.mate.map.*" xmlns:views="com.ny.flex.mate.views.*" xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
- <mx:Script>
- <![CDATA[
- [Bindable]
- public var viewStackSelectedIndex :int = 0;
- ]]>
- </mx:Script>
- <span style="color: rgb(255, 102, 0);"><strong><em><map:BuddyListEventMap/></em></strong></span>
- <mx:HBox horizontalAlign="center" verticalAlign="top" width="100%" height="100%" y="0" x="0">
- <mx:ViewStack id="viewStack" resizeToContent="true" selectedIndex="{viewStackSelectedIndex}" >
- <views:LoginView />
- <views:BuddyListView/>
- </mx:ViewStack>
- </mx:HBox>
- </mx:Application>
2. 建立LoginView :
- <?xml version="1.0" encoding="utf-8"?>
- <mx:Panel xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" width="300" height="200" horizontalAlign="center" verticalAlign="middle" title="Flex Cirngorm Login">
- <mx:Script>
- <![CDATA[
- import com.ny.flex.mate.event.LoginEvent;
- import com.ny.flex.mate.vo.User;
- import mx.validators.Validator;
- private function login():void{
- if(Validator.validateAll(validators).length == 0){
- var loginUser:User = new User();
- loginUser.userName=username.text;
- loginUser.password=password.text;
- <span style="color: rgb(255, 102, 0);"><strong><em>var loginEvent:LoginEvent = new LoginEvent(LoginEvent.LOGIN);
- loginEvent.loginUser = loginUser;
- dispatchEvent(loginEvent);</em></strong></span>
- }
- }
- ]]>
- </mx:Script>
- <!-- Validators-->
- <mx:Array id="validators">
- <mx:StringValidator id="userNameValidator" source="{username}" property="text" required="true"/>
- <mx:StringValidator id="passwordValidator" source="{password}" property="text" required="true" />
- </mx:Array>
- <mx:Form id="loginForm" x="0" y="0">
- <mx:FormItem label="Username:" >
- <mx:TextInput id="username" />
- </mx:FormItem>
- <mx:FormItem label="Password:" >
- <mx:TextInput id="password" displayAsPassword="true" />
- </mx:FormItem>
- <mx:FormItem direction="horizontal" verticalGap="15" paddingTop="5" width="170">
- <mx:Button id="loginBtn" label="Login" click="login()"/>
- </mx:FormItem>
- </mx:Form>
- </mx:Panel>
从上面可以看出在方法login()中发送(dispatch)了LoginEvent, 来看看LoginEvent代码:
- package com.ny.flex.mate.event
- {
- import com.ny.flex.mate.vo.User;
- import flash.events.Event;
- public class LoginEvent extends Event
- {
- public static const LOGIN:String = "login";
- public var loginUser:User;
- public function LoginEvent(type:String, <span style="color: rgb(255, 102, 0);"><strong><em>bubbles:Boolean=true</em></strong></span>, cancelable:Boolean=false)
- {
- super(type, bubbles, cancelable);
- }
- }
- }
在代码中“bubbles”属性必须为“true”,以使得上层的组件(EventMap)可以处理它。
Mate的魔法完全存在于EventMap中,LoginEvent在这里被标签化处理:
- …..
- <EventHandlers type=”{LoginEvent.LOGIN}“>
- <RemoteObjectInvoker destination=”flexmvcRO”
- method=”authenticate”
- arguments=”{event.loginUser}“>
- <resultHandlers>
- <MethodInvoker generator=”{<span style="color: rgb(255, 102, 0);"><strong><em>LoginService</em></strong></span>}”
- method=”<span style="color: rgb(255, 0, 0);"><strong><em>onResult_Authenticate</em></strong></span>”
- arguments=”{<span style="color: rgb(128, 0, 0);"><strong><em>resultObject</em></strong></span>}“/>
- </resultHandlers>
- </RemoteObjectInvoker>
- </EventHandlers>
- ……
在EvevntHandler代码中,用户可以定义service函数(RemoteObjectInvoker),同时也定义了结果处理的类、方法和参数。
来看看 LoginService.as代码::
- package com.ny.flex.mate.service
- {
- import com.ny.flex.mate.vo.User;
- public class <span style="color: rgb(255, 102, 0);"><strong><em>LoginService</em></strong></span>
- {
- [Bindable]
- public var authUserName:String;
- [Bindable]
- public var viewStackSelectedIndex:int ;
- public function <span style="color: rgb(255, 0, 0);"><strong><em>onResult_Authenticate</em></strong></span>(<span style="color: rgb(128, 0, 0);"><strong><em>user:User</em></strong></span>):void{
- authUserName = user.userName;
- viewStackSelectedIndex = 1;
- }
- }
- }
Service类处理结果,返回绑定的对象。然后我们就需要更新目标视窗。
Mate另一个闪光点就是注射(Injecting)可绑定的对象到目标视窗!用户只需要在EventMap类中增加另一个标签Injector。(Coolest 部分):
- <Injectors target=”{BuddyListView}“>
- <PropertyInjector targetKey=”authUserName”
- source=”{LoginService}”
- sourceKey=”authUserName“/>
- </Injectors>
- <Injectors target=”{Flex_Mate}“>
- <PropertyInjector targetKey=”viewStackSelectedIndex”
- source=”{LoginService}”
- sourceKey=”viewStackSelectedIndex“/>
- </Injectors>
在此定义目标视窗、目标关键词,资源服务对象和资源关键词。 你根本不需要写任何glue code。
最后定义目标视窗: BuddyListView.mxml:
- <?xml version="1.0" encoding="utf-8"?>
- <mx:Panel xmlns:mx="http://www.adobe.com/2006/mxml" title="Buddy List of {authUserName}" creationComplete="getBuddyList()" width="500" height="320">
- <mx:Script>
- <![CDATA[
- import mx.collections.ArrayCollection;
- import com.ny.flex.mate.event.GetBuddyListEvent;
- [Bindable]
- public var authUserName:String;
- [Bindable]
- public var buddyCollection:ArrayCollection;
- private function getBuddyList():void{
- var getBuddyListEvent:GetBuddyListEvent = new GetBuddyListEvent(GetBuddyListEvent.GET_BUDDY_LIST);
- getBuddyListEvent.authUserName = authUserName;
- dispatchEvent(getBuddyListEvent);
- }
- ]]>
- </mx:Script>
- <mx:DataGrid id="buddyList" dataProvider="{buddyCollection}" borderStyle="none" width="100%" height="100%" >
- <mx:columns>
- <mx:DataGridColumn dataField="firstName" headerText="First Name"/>
- <mx:DataGridColumn dataField="lastName" headerText="Last Name"/>
- </mx:columns>
- </mx:DataGrid>
- </mx:Panel>
整个开发流程是这样的:
Action–>Dispatch Event–>Config Handler–>create service–>Inject Bindable Object –>another Action….
整个项目结构图 见附件
总结:
在我5篇blog中讨论过的Flex编程框架中,哪一个是最好的呢?
我认为中央管理(central management)最适合进阶水准的小型项目。因为无须学习新的框架,并且也一样有清晰的架构。 而且 他也是走向框架的起点。
对于Mate和Cairngorm,在我看来Mate略占上风,原因如下:
对于Cairngorm:
1,Cairngorm过于复杂,学习曲线较高
2,我觉得Cairngorm有一些垃圾代码(例如Frontcontroller,event 和Command)。
对于Mate:
1,比Cairngorm更简单易学,貌似继承和发扬了Flex的特质。
2,从EventMap中得益,因为无须编写在事件和服务之间的黏着代码。
但另一个方面, EventMap也会带来痛苦,试想一下,用户有50个行为和100个对象需要绑定,那就需要在EventMap中写入大量的configration代码。那么EventMap就成为一个灾难。
因此,如果用户使用Cairngorm,就可以选用 Cairngorm without FrontContoller的方案。
对于Mate,如果使用Meta标签来代替EventMap,就无须设置太多的东西,示例如下:
[EventHadler ={name ="myHandler", serviceclass="myservice" result , taget ...}]
MateDispatch(myevent).
我在期待着它的来临。