Flex代码组织结构的一点经验

在开始设计部门内部太极项目二期的时候,就一直在反思一期工作中的哪些不足以及给组内同学带来的痛苦。
其中最让我苦恼的是界面的设计和开发,我很懒,当时在用EXTjs写界面的时候就想,如果每种界面都像VB的程序一样拖拖拉拉就可以搞出一个看似专业的界面就好了。再结合组内同学对界面的恐惧,我征求大家的意见后,决定在二期采用Flex做一个副客户端展示层。
这个决定挺痛苦的,EXTjs刚用熟练,就要扔掉重新去学Flex,而且要重做这个页面,想想都头疼。
我不会Flex,当初用EXTjs也是现学现卖,所以我觉得不用太复杂的技巧和功能的话,学习成本不会大。但有一点我很关注,就是Flex这种需要编译的语言和JavaScript的动态有很大的不同,我在一期设计EXTjs的代码组织方式的时候,得益于JS的动态执行,每个开发者负责一个独立的js文件,所有的事情都在自己这个文件里面做,不管对错,都不影响其它同学。但Flex如何把代码按照业务逻辑分开,这个我想了好久,下面是我最后的设计方案,我觉得可行性还不错,希望熟悉FLEX的前辈能过来指导下。

=========================FLEX代码组织结构========================

我想要做如下的界面,
每个组员负责菜单上的某个功能模块,
每个组员的代码和其它同学的代码完全隔离开,
但要可以和主窗体交互,这就要求每个模块的包中留有给主窗体代码的CallBack接口。
界面、菜单和大体布局如下:

[img]http://dl.iteye.com/upload/picture/pic/51177/b8c91941-ed52-3169-9cd2-722337caa1ae.jpg[/img]


以菜单为切入点讨论下,
我希望每个同学负责一个或几个模块,并且希望他们对每个模块的实现放到自己独立的一个代码文件夹内。
由于各模块的菜单是由各模块的实现者定义的,主窗体无法事先知道,所以必须在主窗体初始化的时候,通过初始化的回调函数得到各个模块的菜单项,然后拼凑起来,再赋值给主窗体的Menu控件展现出去。这里的代码我是这样设计的:

代码的文件结构:

[img]http://dl.iteye.com/upload/picture/pic/51181/2fccf521-18ff-3490-8a52-e38d13fe6ef6.jpg[/img]

在主窗体代码中这样引用:

   import com.aliyun.taiji.includes.help.*;
import com.aliyun.taiji.includes.project.*;
import com.aliyun.taiji.includes.report.*;
import com.aliyun.taiji.includes.scenario.*;
import com.aliyun.taiji.includes.session.*;
import com.aliyun.taiji.includes.user.*;



主窗体加载后的回调函数,这里会做一些初始化菜单的操作:

<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" borderColor="#B7BABC" initialize="initMain(event)">


private function initMenu():void{
trace("主窗体初始化");
/*
菜单各模块负责自己的菜单数据
但必须按照设计者的约定
使用menu变量保存各模块的菜单XML数据
* */
p_menu_root.appendChild(com.aliyun.taiji.includes.help.util.menu);
p_menu_root.appendChild(com.aliyun.taiji.includes.project.util.menu);
p_menu_root.appendChild(com.aliyun.taiji.includes.report.util.menu);
p_menu_root.appendChild(com.aliyun.taiji.includes.scenario.util.menu);
p_menu_root.appendChild(com.aliyun.taiji.includes.session.util.menu);
p_menu_root.appendChild(com.aliyun.taiji.includes.user.util.menu);

p_menu_list = p_menu_root.menuitem;
p_menu.dataProvider = p_menu_list;

trace("输出菜单XMLLIST");
trace(p_menu_list);
}



在每个模块的代码中都会实现一个叫做menu的静态变量,这个变量统一名称,用来保存各个模块的菜单数据,所以主窗体代码可以在编译的时候就知道,于是可以直接引用。

模块内的menu变量如下:

 public static var menu:XML = 
<menuitem label="报表管理" ModuleID="3">
<menuitem label="功能测试报表" ModuleID="3">
<menuitem label="旺旺客户端" ModuleID="3"/>
<menuitem label="监控系统" ModuleID="3">
<menuitem label="准确率测试" CallBackID="1" ModuleID="3"/>
<menuitem label="算法覆盖率测试" CallBackID="2" ModuleID="3"/>
<menuitem label="自动化测试" CallBackID="3" ModuleID="3"/>
</menuitem>
</menuitem>
<menuitem label="性能测试报表" ModuleID="3">
<menuitem label="旺旺客户端" ModuleID="3"/>
<menuitem label="监控系统" CallBackID="4" ModuleID="3"/>
</menuitem>
</menuitem>



这样菜单就可以动态的被拼装起来,无论以后如何更改菜单数据,主窗体的代码无需变动。但是菜单数据有了,每个菜单的动作却是不一样的,为了避免耦合,这是onclick的响应事件一定要由各个模块来完成,但在模块代码变更的时候如何又不影响主窗体代码呢?下面是我的解决方案。

由于用户的行为都是从菜单点击进来的,
所以我给每个模块一个ModuleID,这样主程序可以知道去访问哪个模块的CallBack函数去处理这个点击事件。
但模块是主程序所知道的,各个模块的菜单是由每个模块的负责人自己定义的,不应该放到主程序中处理,否则过于耦合,不方便扩展。
于是每个菜单项的一个ID就被当作CallBack函数的一个参数回传给所属模块,让模块处理自己的每个菜单项事件。不过需要定义这个ID的名字,而且要每个模块统一,这样主程序才能知道如何去访问这个属性,我将它命名为CallBackID。

我将模块按照package来管理,在每个package内实现自己的逻辑,并且提供CallBack函数。
比如点击菜单这个事件就需要CallBack函数来访问对应模块内部的代码处理。
在主程序内我是这样处理的

private function menuEvent(event:MenuEvent):void{
trace("菜单事件触发");
/*
ModuleID用来区分菜单模块
CallBackID为子菜单项表示,
由各模块负责人自己定义,需要回传给util.menuCallBack()
* */

/*
ModuleID是菜单上每块业务逻辑的ID
每个util.menu都必须有ModuleID属性,这个是给Flex框架主程序区分菜单模块调用的,
这个值由崔峥统一分配。
如下:
1 => 项目-pronject
2 => 场景-scenario
3 => 报表-report
4 => 用户-user
5 => 登陆及其他-session
6 => 帮助-help
7以后 => 冗余(监控等后续模块)-other

每个util.menu还必须有CallBackID属性,这个是给每个菜单模块响应点击事件的回调函数用的,
这个值将由各个菜单模块的Owner自行设计。
* */
switch(event.item.@ModuleID.toString()){
case "1":
west_tree.dataProvider = null;
break;
case "2":
west_tree.dataProvider = null;
break;
case "3":
com.aliyun.taiji.includes.report.util.menuCallBack(event.item.@CallBackID,p_obj_map);
break;
case "4":
west_tree.dataProvider = null;
break;
case "5":
west_tree.dataProvider = null;
break;
case "6":
west_tree.dataProvider = null;
break;
case "7":
west_tree.dataProvider = null;
break;
default:
Alert.show(event.item.@ModuleID+" 不是可用的菜单项。");
}
trace(event.item);
}



点击某一菜单后界面变化:

[img]http://dl.iteye.com/upload/picture/pic/51179/9ee6a101-e935-34fd-be98-517c4a6fb223.jpg[/img]


当菜单点击的时候,主窗体先响应点击事件,得到Event对象的ModuleID后,主窗体就知道给哪个模块的CallBack函数去处理该事件。但模块的子菜单如何处理,并不是主窗体代码能够关心到的,这里就把CallBackID回传过去,让模块的CallBack函数去处理。

拿一个模块的代码举例:

package com.aliyun.taiji.includes.report
{
import mx.containers.Panel;
import mx.controls.Tree;

public class util
{
private static var west_tree:Tree;
private static var certain_panel:Panel;
private static var west_panel:Panel;

public static function menuCallBack(CallBackID:String,obj:Object):void{

if(obj["west_tree"]){
west_tree = obj["west_tree"];
}else{
throw new Error("Report模块 找不到对象 west_tree");
}
if(obj["certain_panel"]){
certain_panel = obj["certain_panel"];
}else{
throw new Error("Report模块 找不到对象 certain_panel");
}
if(obj["certain_panel"]){
west_panel = obj["west_panel"];
}else{
throw new Error("Report模块 找不到对象 west_panel");
}

certain_panel.title = "报表";

switch(CallBackID){
//报表——>功能——>监控系统——>准确率测试(@CallBackID=1)
case "1":
west_panel.title = "准确率测试";
west_tree.dataProvider = treedata_baobiao_shurufa_gongneng_zhunquelv.node;
break;
//报表——>性能——>监控系统(@CallBackID=4)
case "4":
west_panel.title = "监控系统";
west_tree.dataProvider = treedata_baobiao_shurufa_xingneng.node;
break;
default:
break;
}

}
}


主窗体在得到响应事件的时候,就会通过event对象的ModuleID属性和switch分支找到需要处理该事件的模块,然后执行CallBack函数,比如:

com.aliyun.taiji.includes.report.util.menuCallBack(event.item.@CallBackID,p_obj_map);


上面这个代码我们看到CallBack方法中还有其它的参数,这些很重要。
比如我们为了让各个模块的代码操作主窗体,我们必须把它需要操作的控件通过CallBack传进去,否则我们将看不到模块的代码的UI行为。
这里,p_obj_map参数就是这么一个容器,它是主窗体主要控件的一个HASH集合,实现如下:

public var p_obj_map:Object = new Object();

private function initMenu():void{
trace("主窗体初始化");

/*
p_obj_map是用来存储给各个菜单模块回调函数参数数据的HASH结构
使用前务必检查结构体中是否存在需要的对象
* */
p_obj_map["west_tree"] = west_tree;
p_obj_map["west_panel"] = west_panel;
p_obj_map["certain_panel"] = certain_panel;

trace("遍历回调对象MAP结构");
var classInfo:Object = ObjectUtil.getClassInfo(p_obj_map);
for each(var q:QName in classInfo["properties"]){
trace(q.localName+"=>"+p_obj_map[q.localName]);
}

/* 若结构中不存在所要对象
将返回undefined
* */
trace(p_obj_map["nil"]);
}


实际上p_obj_map就是一个Object对象,或者说是个json对象,我在这里将它当作HASH来用,挺方便的。
在p_obj_map被传递回模块内部时,各模块可以通过预先定义好的key键值得到所要的窗体对象,代码如下:

private static var west_tree:Tree;
private static var certain_panel:Panel;
private static var west_panel:Panel;

public static function menuCallBack(CallBackID:String,obj:Object):void{

if(obj["west_tree"]){
west_tree = obj["west_tree"];
}else{
throw new Error("Report模块 找不到对象 west_tree");
}
if(obj["certain_panel"]){
certain_panel = obj["certain_panel"];
}else{
throw new Error("Report模块 找不到对象 certain_panel");
}
if(obj["certain_panel"]){
west_panel = obj["west_panel"];
}else{
throw new Error("Report模块 找不到对象 west_panel");
}
}


整个Flex的UI代码框架就是这样,好处是各个模块的任何变更都不影响其它模块和主窗体,开发人员可以独立的专注的做自己的事情了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值