[学习笔记]Flex框架Cairngorm Step by Step
教程来自:http://www.davidtucker.net/
E文
作者已经写了5步,这里是我学习过程中的体会
同时也算是一个简单的翻译吧。
重要说明 :本帖中绝大部分是个人学习笔记,很少有对原文的逐句翻译。(所以如果我写得比较乱希望不要以为原作者也是我这水平 - -||www.gdutbbs.com)
本教程适合:
[*]flex学习者
[*]有一定入门知识的对框架,设计模式的感兴趣的初学者
[*]看热闹者
如果你想一步一步编码实现下去你必需至少有:
[*]Flex 3 SDK 或者 Flex 3 Builder (本教程不涉及如何用FlexSDK命令行编译和如何使用FlexBuilder)
[*]本教程涉及的框架的SWC文件(较小)
[ 本帖最后由 iptton 于 2008-5-10 14:18 编辑 ]
Step1
第一步:Getting start其实第一步与cairngorm框架的关系不大,只是一个简化版的MVC的实现方式
这里假设你已经对两个概念:普通WEB应用与RIA(Ajax)应用的不同有一定认识。
很大程度上可以说 RIA应用 是一种C/S 架构的应用,犹其从开发者的角度来看。
这时候,页面跳转时的数据共享就显得比较麻烦了(不懂flex的可以跳过了..)
比如,前一页面的的状态全局的某些数据状态
在普通web应用中一般是把这类数据放在session或cookies里
无论session还是cookies对客户来说都是唯一的。
在flash应用中如果要实现MVC,无疑Model就扮演着类似于session或cookies的角色
(或者我们可以直接就用cookies来实现model?嗯 ,这个想法在小型应用中应该可行,只是cookies不是对象,只支持字符串变量,不够方便。)
既然Model要是唯一的,这时我们很自然就能想到用单例(Singleton)模式。
之前我发过一张贴 as3的设计模式学习笔记 的帖。
由于as3不允许有private的构造函数,,在as3中实现singleton就有点曲折了:
package com.gdutbbs.iptton{
public class Model{
public static instance:Model;
public function Model(force:InternalClass){
if (enforcer == null) {
//这句的作用:针对这样的调用:new MyModule(null)
//只能抛出异常,实际上新的实例还是被构造了!
throw new Error( "试图破坏单例模式?" );
}
}
public static function getInstance():Model{
if(instance==null){
instance=new Model(new InternalClass());
}
return instance;
}
}
}
// 单例的曲折实现..因为AS3不支持private的构造函数
// 原理:这里的SingletonEnforcer类只能在本文件内可见(internal)
class InternalClass{}
假设一个最常见的系统登陆,可以分为两个视图:一是登陆前,一是登陆成功后(当然,还可以细分为登陆失败)
可以分别给两个视图做成组件:分别在 views.loginForm 和 views.welcome
这里主程序的代码可能是这样
这里用了viewStack。(如果你学过swing的对viewStack应该不陌生)
简单点说viewStack就是一个tabView处在viewStack里的每个组件同一时间只能显示一个。
显示哪一个由viewStack的selectedIndex指定。
学过flex的应该能想到绑定binding这个概念,
这里用的就是绑定了,viewStack的selectedIndex通过对MyModel的属性currentState绑定来实现来获取当前状态以便决定当前应当显示哪个视图。
main.mxml
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
xmlns:v="views.*"
>
<mx:Script>
<![CDATA[
import Models.MyModel;
public var m:MyModel=MyModel.getInstance();
]]>
</mx:Script>
<mx:ViewStack selectedIndex="{m.currentState}" width="100%" height="100%">
<v:loginForm width="100%" height="100%"/>
<v:welcome width="100%" height="100%"/>
</mx:ViewStack>
</mx:Application>
接下来是两个视图对model:
models/welcome.mxml
<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Script>
<![CDATA[
import Models.MyModel;
public var m:MyModel=MyModel.getInstance();
]]>
</mx:Script>
<mx:VBox>
<mx:Label fontSize="20" text="WELCOME!!!欢迎光临!!" />
<mx:Button click="{m.currentState=MyModel.LOGIN}" label="LOGOUT>>退出" />
</mx:VBox>
</mx:Canvas>
models/loginForm.mxml
<?xml version="1.0" encoding="utf-8"?>
<mx:TitleWindow xmlns:mx="http://www.adobe.com/2006/mxml"
layout="horizontal">
<mx:Script>
<![CDATA[
import Models.MyModel;
public var m:MyModel=MyModel.getInstance();
]]>
</mx:Script>
<mx:Form>
<mx:FormItem label="userName用户名:">
<mx:TextInput id="ti_uname" />
</mx:FormItem>
<mx:FormItem label="password密码:">
<mx:TextInput displayAsPassword="true" id="ti_pwd" />
</mx:FormItem>
<mx:FormItem label="">
<mx:Button label="登陆LOGIN" id="bt_submit" click="{m.currentState=MyModel.LOGIN}"/>
</mx:FormItem>
</mx:Form>
</mx:TitleWindow>
models/MyModel.as
package Models{
import com.adobe.cairngorm.model.IModelLocator;
public class MyModel implements IModelLocator{
// 保存实例的变量
private static var instance:MyModel;
public function MyModel(enforcer:SingletonEnforcer) {
if (enforcer == null) {
//这句的作用:防止这样的调用:new MyModule(null)
//只能抛出异常,实际上新的实例还是被构造了!
throw new Error( "You Can Only Have One ModelLocator" );
}
}
// 返回实例
public static function getInstance() : MyModel {
if (instance == null) {
instance = new MyModel( new SingletonEnforcer );
}
return instance;
}
public var currentState:uint=0;
//以下常量的做法实际上并不好,与主程序的布局顺序相关联了!
public static const LOGIN:uint=0;
public static const WELCOME:uint=1;
}
}
// 单例的曲折实现..因为AS3不支持private的构造函数
// 原理:这里的SingletonEnforcer类只能在本文件内可见(internal)
class SingletonEnforcer {}
=====================================================================================
收获:
在学这个之前对多视图跳转时的状态保存
我的方法是在主程序里加一个公共变量(相当于全局变量)
然后在每个组件里通过 Application.application.globalVariable来引用
这样的最大不好之处我个人认为是组件与主程序间的藕合太密切了。
尽管知道singleton也在java课程设计中使用过但时当时还是没想到这个可以这样做
当然,本例中各视图还是离不开model这个约束,如何解除这个藕合?下一帖和你说..
[ 本帖最后由 iptton 于 2008-5-9 22:49 编辑 ]
step2
第二步。。。。原文只说了两件事:viewStack中的selectedIndex和用常量消除“魔数"(magic number)。
本帖中第一步已有说明,对于魔数的处理属于编程素质的问题,这里就不讨论了。。
[ 本帖最后由 iptton 于 2008-5-9 22:22 编辑 ]
step3
第三步。。。现在MVC有了Model 和 View 只欠一个Controller了
这一步的内容包括:
1,Event
2,FrontController
3,Command
事件模型在flash里占有很大份量,相关内容可以看看这里,如果看不明白建议翻下相关文档(flexbuilder自带文档就很不错)
原网站上对MVC的描述:
有了对“事件”,view就可以轻松解除对model的依赖了
当然,如果你了解了什么是事件,想必对观察者(observer)模式不会陌生
当登陆按钮被点击时,发布LoginEvent事件。
models/loginForm.mxml
<?xml version="1.0" encoding="utf-8"?>
<mx:TitleWindow xmlns:mx="http://www.adobe.com/2006/mxml"
layout="horizontal">
<mx:Script>
<![CDATA[
import Models.*;
import events.*;
private var m:MyModel=MyModel.getInstance();
private function login():void{
var u:String=ti_uname.text;
var p:String=ti_pwd.text;
//这里可以做一些如检查输入是否为空的操作然后再触发事件
//本例从简
var le:LoginEvent=new LoginEvent(u,p);
le.dispatch();//这里的事件分发形式有点奇特。。
}
]]>
</mx:Script>
<mx:Form>
<mx:FormItem label="userName用户名:">
<mx:TextInput id="ti_uname" />
</mx:FormItem>
<mx:FormItem label="password密码:">
<mx:TextInput displayAsPassword="true" id="ti_pwd" />
</mx:FormItem>
<mx:FormItem label="">
<mx:Button label="登陆LOGIN" id="bt_submit" click="login()"/>
</mx:FormItem>
</mx:Form>
</mx:TitleWindow>
再来看看LoginEvent和FrontController:
events/LoginEvent.as
package events{
import com.adobe.cairngorm.control.CairngormEvent;
import flash.events.Event;
public class LoginEvent extends CairngormEvent{
//事件的名称
public static const LOGIN:String="LOGIN";
public var username:String;
public var passwd:String;
public function LoginEvent(
submittedUsername:String,
submittedPassword:String,
bubbles:Boolean=false,
cancelable:Boolean=false
){
this.username=submittedUsername;
this.passwd=submittedPassword;
super(LOGIN, bubbles, cancelable);
}
//因为添加了username和password两个属性,如果用原来的clone函数,会丢掉这两个参数
override public function clone():Event{
return new LoginEvent(username,passwd);
}
}
}
一般意义上的MVC中C负责的是逻辑的处理,而FrontControl这种方式
则是把逻辑方面的事分给了Command (这里是否就是传说中的Command模式?)
FrontController只是事件与Command之关的传话筒。
(据我所知的PHP框架都是用这种MVC,当然,在PHP这种层面应用中,“事件”就是用户的请求)
control/MyController.as
package control{
import com.adobe.cairngorm.control.FrontController;
import commands.*;
import events.*;
public class MyController extends FrontController{
public function MyController(){
super();
//绑定登陆事件与登陆处理逻辑
//是不是很像 addEventListener(someEvent, someCallBack ) ?
//只不过不同的是这里的callBack是class
//在事件触发时才被实例化并肯执行其excute函数
this.addCommand(LoginEvent.LOGIN,LoginCommand);
}
}
}
对登陆的逻辑处理:
commands/LoginCommand.as
package commands{
import Models.*;
import com.adobe.cairngorm.commands.ICommand;
import com.adobe.cairngorm.control.CairngormEvent;
import events.*;
public class LoginCommand implements ICommand{
public var m:MyModel=MyModel.getInstance();
public function LoginCommand(){
//所有的Command类都是工厂模式中被构造?
}
//被动调用,这个是vistor模式还是Observe模式?
public function execute(event:CairngormEvent):void{
var le:LoginEvent = event as LoginEvent;
if( this._checkLogin(le.username,le.passwd) ){
//登陆成功
//进入欢迎界面
m.currentState=MyModel.WELCOME;
}else{
//发布登陆失败事件
}
}
private function _checkLogin(u:String,p:String):Boolean{
//这里应该有对主机的请求,下回说明,这里只作一个简单的模拟
if(u=="a" && p=="a"){
return true;
}
return false;
}
}
}
在主程序里引入FrontController:
main.mxml
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
xmlns:v="views.*"
xmlns:c="control.*"
>
<mx:Script>
<![CDATA[
import Models.MyModel;
public var m:MyModel=MyModel.getInstance();
]]>
</mx:Script>
<c:MyController id="controller" />
<mx:ViewStack selectedIndex="{m.currentState}" width="100%" height="100%">
<v:loginForm width="100%" height="100%"/>
<v:welcome width="100%" height="100%"/>
</mx:ViewStack>
</mx:Application>
=======================================================================
收获:
加深了对FrontController的理解
另外,对如何使用框架有了新的想法,
有这样一句话:
the best solution is not when there is nothing left to add, but when there is nothing left to take away!
最好的解决方案不是无物可加,而是无物可减!
[ 本帖最后由 iptton 于 2008-5-9 23:07 编辑 ]
step4: 与服务端交互
第四步:这一步新的概念比较多1。 ServiceLocator 可以联系ModelLocator,看成远程数据的Model,一般有三种:HTTPService, WebService, 或RemoteObjec
2。Business Delegate是Command与ServiceLocator的桥梁。有三个功能:定位相应的远程服务,调用该服务,返回该服务的结果。通常每个Service都会有一个业务代理。
3。 Value Object - 只有属性的类,可以看成服务端与客户端数据的公共接口。
使用了以上三个新事物后的事件流:
流程就不多解释了,直接看代码:
在上一步,Command把 Delegate=>sevices=>ServerProcess=>services=>Delegate=>Response 这些工作全模拟完成了。
现在我们可以把焦点放在Command模拟的那部分:
不管Delegate怎么实现,现在Command应该有两个职能:提交远程请求的参数及对远程响应进行处理。
在Flex的rpc里,响应的接口是 IResponse,所以新的LoginCommand应该实现 ICommand和IResponse两个接口。
commands/LoginCommand.as
package commands{
import mx.rpc.IResponder;
import com.adobe.cairngorm.commands.ICommand;
import com.adobe.cairngorm.control.CairngormEvent;
import mx.controls.Alert;
import events.*;
import Models.*;
import business.*;//LoginDelegate;
public class LoginCommand implements ICommand,IResponder{
public var m:MyModel=MyModel.getInstance();
public function LoginCommand(){
//所有的Command类都是工厂模式中被构造?
}
//被动调用,这个是vistor模式还是Observe模式?
public function execute(event:CairngormEvent):void{
var le:LoginEvent = event as LoginEvent;
var delegate:LoginDelegate = new LoginDelegate( this );
delegate.login(loginEvent.loginAttempt);
/*
if( this._checkLogin(le.username,le.passwd) ){
//登陆成功
//进入欢迎界面
m.currentState=MyModel.WELCOME;
}else{
//发布登陆失败事件
//m.currentState=MyModel.LOGIN;
}*/
}
/*
private function _checkLogin(u:String,p:String):Boolean{
if(u=="a" && p=="a"){
return true;
}
return false;
}*/
//实现IResponse的两个接口
public function result( event:Object ):void {
if(event.result == true) {
m.currentState = MyModel.WELCOME;
} else {
mx.controls.Alert.show("Password Incorrect","ERROR");
}
}
public function fault( event:Object ):void {
mx.controls.Alert.show("远程服务出错!");
}
}
}
loginService
<?xml version="1.0" encoding="utf-8"?>
<cairngorm:ServiceLocator
xmlns:cairngorm="com.adobe.cairngorm.business.*"
xmlns:mx="http://www.adobe.com/2006/mxml">
<!-- Login Service -->
<mx:RemoteObject id="loginService"
showBusyCursor="true"
destination="ColdFusion"
source="CairngormTest.CairngormLogin">
<mx:method name="login" />
</mx:RemoteObject>
</cairngorm:ServiceLocator>
[ 本帖最后由 iptton 于 2008-5-9 23:56 编辑 ]
step5
第五步 以前做flex~~好过瘾 。。60个控件要背熟。。。现在做C#混合java,亦好过瘾。。 我只用来做过软件工程课程设计,边做边查API。。。LS是做商业项目吗,背熟控件的API。。。比较。。。 又要重拾AMFPHP了。。。数据库课程设计。。。 对啊。。是商业项目。。soap那类。
页:
[1]