0
回答
对 Cairngorm MVC 框架的进一步扩展与实现
开发十年,就只剩下这套Java开发体系了   

文章来自 IBM developerWorks

Cairngorm 的一些限制

Adobe 的轻量级 MVC 框架 Cairngorm 虽然很强大,但是在真正的企业开发中还存在着一些限制和不足,比如在基于 Cairngorm 的 Application 中只能有一个 ServiceLocator 实例,这样当 Application 中服务比较多的时候不便于管理;前端控制器处理事件和命令的映射是一一对应的,即一个事件类必须对应于一个命令类,这样当系统中的事件比较多的时候,就会 产生大量的小的命令类,这样导致一些逻辑上有联系的命令类不能很好的交互等等缺点。

因此对于它的增强应该允许 Application 定义多个 ServiceLocator,ServiceLocator 之间通过一个唯一标识符 locatorId 来区分,前端控制器应该允许将事件映射到一个命令类中的任何一个方法而不仅仅是 execute 方法。

功能增强介绍

因此我们在项目实际开发中,可以对于 Cairngorm 的这些不足进行增强,在这里只介绍对于 Cairngorm 的增强部分,关于 Cairngorm 的基本内容请参看上一篇文章《基于 Cairngorm MVC 框架的 Flex 程序设计与开发》

RDFEvent: 继承于 Cairngorm 的 CairngormEvent,所有的自定义事件都必须继承于 RDFEvent,位于包 com.ibm.rdf.mvc.controller 中,一般在自定义事件中包含需要传给 Command 的参数。

RDFEventDispatcher: 继承于 Cairngorm 的 CairngormEventDispatcher,是 RDFEvent 的分发器,位于包 com.ibm.rdf.mvc.controller。

IAbstractCommand: 命令接口,继承于 Cairngorm 的 ICommand,位于包 com.ibm.rdf.mvc.command 下,RDF 还提供了一个 AbstractCommand 类,它已经实现了 IAbstractCommand 接口,开发者可以通过继承 AbstractCommand 类覆盖其中的方法来自定义命令类。RDF 的命令类可以提供多个处理业务逻辑的方法,用于自定义的 RDFEvent 子类可以映射到这多个业务逻辑方法,而不是像 Cairngorm 只能映射到 execute 方法。

RDFController: 前端控制器,继承于 Cairngorm 的 FrontController,位于包 com.ibm.rdf.mvc.controller 下,自定义的前端控制器必须继承 RDFController,它主要通过 addMethod 方法完成 RDFEvent 和实现了 IAbstractCommand 接口的类的成员方法之间的映射。这样减少了命令类的个数,相当于将一些逻辑上有关系的方法放到了一个命令类中,简化了交互。如图所示


图 1. 控制器 :
图 1. 控制器 :

CommandResponder:因为 RDF 可以将事件映射到命令类的里的任何一个方法,所以每一个方法都要有自己的回调函数,RDF 提供了 CommandResponder 来解决这个问题,它实现了 Cairngorm 的 IResponder 接口,位于包 com.ibm.rdf.mvc.command。

IAbstractDelegate: 服务代理接口,位于 com.ibm.rdf.mvc.delegate 包下,它里面定义了一些常用的回调函数,RDF 还定义了它的默认实现类 AbstractDelegate,开发者可以继承于 AbstractDelegate 覆盖其中的回调函数,并且定义一些业务逻辑方法,通过在其中调用 ServiceLocator 中定义的服务来实现对服务最终的调用。

ServiceLocator 的增强是多添加一个 locator id 属性,从而使得在一个项目中可以根据模块配置多个 ServiceLocator 文件,在寻找某一个 service 的时候,可以根据 locator id 获得具体的 ServiceLocator。

一个入门的例子

前面我们讲解了扩展的的基本原理,下面我们就用一个简单的员工管理系统的例子来进一步的理解。


图 2. 新建 Flex 项目
图 2. 新建 Flex 项目

Project name 取名为 RDF_MVC, Next


图 3. 配置
图 3. 配置

指定 Target runtime,Finish。

  • 配置环境

将 rdfrsl.swc 放入 Flex Build Path 下,


图 4.
图 4.

将以下 jar 包放入 Java classpath 下,如图所示:


图 5.
图 5.

在 web.xml 中做如下配置,


清单 1.
				 
 <servlet> 
 <servlet-name>RDFXmlRpcServlet</servlet-name> 
 <servlet-class>com.ibm.rdf.rpc.xml.server.RDFXmlRpcServlet</servlet-class> 
 <init-param> 
 <param-name>enabledForExtensions</param-name> 
 <param-value>true</param-value> 
 </init-param> 
 <init-param> 
 <param-name>keepAliveEnabled</param-name> 
 <param-value>true</param-value> 
 </init-param> 
 </servlet> 
 <servlet-mapping> 
 <servlet-name>RDFXmlRpcServlet</servlet-name> 
 <url-pattern>/rdfxmlrpc</url-pattern> 
 </servlet-mapping> 

以上代码配置了 RDFXmlRpcServlet,将其路径映射为 /rdfxmlrpc.

  • 在 flex_src 下建立如下包结构

图 6. 包结构
图 6. 包结构
  • 定义视图

在 RDF_MVC.mxml 下拖入两个 Panel,一个用于录入员工信息,一个用于显示员工信息,录入的员工信息包括 First Name,Last Name,Age,SN( 员工号 ),Single( 是否单身 ) 等,从后端取出来的员工信息存储在 DataGrid 中,


图 7. 界面视图( 查看大图
图 7. 界面视图
  • 定义 ModelLocator

    首先在 model 包中新建一个 ActionScript Class,取名为 EmployeesListModel.as,内容如下,



    清单 2.
    				 
    package  sample.rdf.model 
     { 
       import mx.collections.ArrayCollection;   
        [Bindable ] 
     public class EmployeesListModel 
     { 
     public var employeeList : ArrayCollection = null ; 
    
     } 
     } 

    再新建一个 Actionscript Class,取名为 RemoteModelLocator.as,内容如下



    清单 3.
    				 
     package sample.rdf.model 
     { 
     [Bindable ] 
     public class RemoteModelLocator 
     { 
     public static var modelLocator:RemoteModelLocator; 
    
    				 // 存放员工数据 public var employeesListModel:EmployeesListModel = new EmployeesListModel(); 
       // 单例模式
       public static function getInstance():RemoteModelLocator 
     { 
     if ( modelLocator == null ) 
     { 
     modelLocator = new RemoteModelLocator(); 
     } 
        return modelLocator; 
     } 
     public function RemoteModelLocator() { 
          if ( modelLocator != null ) 
     { 
         throw new Error( "Only one ModelLocator instance should be instantiated" ); 
        }           
     } 
     } 
     } 

  • 定义 VO

    在 Flex 端将每个员工信息封装在一个对象中,在 vo 包中定义 ActionScript class,取名为 Employee.as,代码如下,



    清单 4.
    				 
    package  sample.rdf.vo 
    {
    	[RemoteClass(alias='sample.rdf.domain.Employee')]
    		public class Employee     
    {
    private var _firstName : String;
    		
    public  var lastName : String;
    
    private var _age : int;
    		
    public var isSingle : Boolean;
    		
    public var skills : Array = ["Java","Flex","C++"];
    		
    public var onboardDate:Date = new Date();
    		
    public var serilzerNum : Number;
    		
    public function get age() : int
    {
    	return this._age;
    } 
    
    public function set age(age : int) : void 
    {
        this._age = age;
    } 
    public function get firstName() : String
    {
        return this._firstName;
     } 
    
    public function set firstName(firstName : String) : void 
    {
       this._firstName = firstName;
    			} 
    		} 
    } 

    这里的 [RemoteClass(alias='sample.rdf.domain.Employee')] 用于将 ActionScript 的对象与 java 类 sample.rdf.domain.Employee 的对象映射起来。

  • 定义事件

    在 events 包中新建一个 ActionScript class,取名为 EmployeeEvent.as,代码如下:



    清单 5.
    				 
    package  sample.rdf.events
    {
    import com.ibm.rdf.mvc.controller.RDFEvent;
    	
    import sample.rdf.vo.Employee;
    	
    public class EmployeeEvent extends RDFEvent 
    	{
     	public var employee : Employee;
    		
    	public var remoteType:Object;
    			
     //第一个参数为事件类型
        public function EmployeeEvent(type : String, 
          employee:Employee = null,remoteType:Object = null)
    			{
    				super(type);
    				this.employee = employee;
    				this.remoteType = remoteType;
    			}
    		}
    }

    在构造函数中将 VO 作为参数传进去,以便 command 使用。

  • 在页面中分发事件

    在 RDF_MVC.mxml 的 <mx:Script> 下面添加如下代码:



    清单 6.
    				 
     [Bindable]
    public var employeModel:EmployeesListModel = 
          RemoteModelLocator.getInstance().employeesListModel;
    	//添加Employee
    	private function addEmployee():void
    	{
        var employee:Employee = new Employee();
    	employee.firstName = firstname.text;
    	employee.lastName = lastname.text;
    	employee.age = parseInt(age.text);
    	employee.serilzerNum = parseFloat(sn.text);
    	employee.isSingle = issingle.value;
        var event:EmployeeEvent = new EmployeeEvent(
        RemoteControl.ADD_EMPLOYEE_DATA,employee,remotetype.value);
    	RDFEventDispatcher.dispatchEvent(event);
    }
    //获取Employee列表
    private function listEmployees():void
    	{
    var event:EmployeeEvent = 
    new EmployeeEvent(RemoteControl.GET_EMPLOYEES_DATA,null,remotetype.value);
    RDFEventDispatcher.dispatchEvent(event);		  
    }

    点击 Add Employee 按钮触发 addEmployee 方法,点击 Get All Employees List 按钮触发 listEmployees 方法。

    当事件分发后,前端控制器 RemoteControl 将事件映射到某个命令类的方法上,这里前端控制器类 RemoteControl 还没有定义,下面定义。

  • 定义前端控制器

    事件分发后,应该执行一个相应的方法,方法通过执行相应的业务逻辑,那到底事件和命令类的成员方法之间是如何映射的呢?

    在包 control 中新建一个 RemoteControl.as,代码如下:



    清单 7.
    				 
    package  sample.rdf.control
    {
    	import com.ibm.rdf.mvc.controller.RDFController;
    	import sample.rdf.commands.EmployeeCommand;
    	public class RemoteControl extends  RDFController
    	{
    	// 事件名称
    	    public static const ADD_EMPLOYEE_DATA : String = "addEmployee";
    		public static const GET_EMPLOYEES_DATA : String = "getEmployees";
    		// 将具体事件和command类中的方法绑定起来
    		public function RemoteControl()
    		{
    		trace("register Remoting  Command");
         addMethod( RemoteControl.ADD_EMPLOYEE_DATA, EmployeeCommand, "addEmployee");	
         addMethod( RemoteControl.GET_EMPLOYEES_DATA, EmployeeCommand, "getEmployees");
    			}	
    		}
    }

    第一个参数是事件的名称 ( 类型 ),第二个参数是具体的命令类,第三个参数是具体的命令类中的方法名。

    这里将两个事件 (RemoteControl.ADD_EMPLOYEE_DATA,RemoteControl.GET_EMPLOYEES_DATA) 映射到了EmployeeCommand的两个方法上 (addEmployeegetEmployees)。这里EmployeeCommand还没有定义,下面定义。

  • 定义 Command

    然后再定义 EmployeeCommand.as,代码如下:



    清单 8.
    				 
    package sample.rdf.commands
    {
    import com.ibm.rdf.mvc.command.CommandResponder;
    import com.ibm.rdf.mvc.controller.RDFEvent;
    	
    import mx.collections.ArrayCollection;
    import mx.rpc.events.FaultEvent;
    	
    import sample.rdf.business.EmployeeDelegate;
    import sample.rdf.events.EmployeeEvent;
    import sample.rdf.model.RemoteModelLocator;
    import sample.rdf.vo.Employee; 	
    	
    public class EmployeeCommand extends BaseCommand  
    {
      public var modellocator:RemoteModelLocator = RemoteModelLocator.getInstance();
            //增加员工
    	public function addEmployee(event:RDFEvent): void
    	{
    //将参数取出
       	 	var employEvent:EmployeeEvent = EmployeeEvent(event); 
       var employee:Employee = employEvent.employee;
    //设定回调函数
    var commandResponder:CommandResponder = 
        new CommandResponder(addEmployeeSuccess,addEmployeeFault);
    
    	commandResponder.addBeforeSuccessCallBack(beforeSuccessCallBack);
    	commandResponder.addBeforeFaultCallBack(beforeFaultCallBack);
    	commandResponder.addBeforeCallBack(beforeCallBack);
    var delegate:EmployeeDelegate = new EmployeeDelegate(commandResponder);
    	
    var remoteType:String = employEvent.remoteType.toString();
    if(remoteType == "0")  //XML
    {
       	   	 delegate.addEmployeeByXML(employee);
    }
    	}
    	
    	//获得员工列表
    	public function getEmployees(event:RDFEvent): void
    	{
    var employEvent:EmployeeEvent = EmployeeEvent(event); 	//设定回调函数
    var commandResponder:CommandResponder = 
         new CommandResponder(getEmployeesSuccess,getEmployeesFault);
    	commandResponder.addBeforeSuccessCallBack(beforeSuccessCallBack);
    	commandResponder.addBeforeFaultCallBack(beforeFaultCallBack);
    	commandResponder.addBeforeCallBack(beforeCallBack);
    var delegate:EmployeeDelegate = new EmployeeDelegate(commandResponder);
    	
    var remoteType:String = employEvent.remoteType.toString();
    if(remoteType == "0")  //XML
    {
       	    	 delegate.getEmployeesByXML();
    }
        	     
    	}….	
    }
    }

    这里代理还没有定义,下面定义服务代理。

  • 定义服务代理

    然后再定义 EmployeeDelegate.as,代码如下:



    清单 9.
    				 
    package sample.rdf.business
    {
    import com.ibm.rdf.mvc.rpc.response.IResponseToken;
    import com.ibm.rdf.mvc.rpc.service.IRemoteService;
    import com.ibm.rdf.mvc.rpc.service.locator.RDFServiceLocator;
        
    import sample.rdf.vo.Employee;
           	
    		public class EmployeeDelegate extends  BaseDelegate
    		{							
    			public function EmployeeDelegate(responder:IResponder)
    			{
    				super(responder);
    			}	
    
    			//添加employee
    			public function addEmployeeByXML(employee:Employee):void
    			{
    				//getInstance的参数是唯一标识service locator的locatorId
    var service:IRemoteService = 
    RDFServiceLocator.getInstance("services1").getRemoteService("
              employeeServiceByXMLRPC");
    //invoke 的第一个参数是后台 Java 后台服务暴露出的方法
     var token:IResponseToken = service.invoke("addEmployee",employee);
    				//设定两个回调函数
     var responder:mx.rpc.Responder = new mx.rpc.Responder(addEmployeeOnResult,OnFault);
     token.addResponder(responder);
    			}	
    			//获得employee	列表				
    			public function getEmployeesByXML():void
    			{
    var service:IRemoteService = 
    RDFServiceLocator.getInstance("services1").getRemoteService("
          employeeServiceByXMLRPC");
    var token:IResponseToken = service.invoke("getDepartmentInfo");
    				//设定回调函数
    var responder:mx.rpc.Responder = new mx.rpc.Responder(getEmployeesOnResult,OnFault);
    				token.addResponder(responder);  
    			}
    								
    …
    }
    }

    这里定义的 addEmployeeByXML 和 getEmployeesByXML() 方法从 RDFServiceLocator 里面获取相应的服务并且调用。

  • 定义 RDFServiceLocator

    在 flex_src 下新建一个 MXML Component,取名为 XMLServices.mxml,代码如下:



    清单 10.
    				 
     <?xml version="1.0" encoding="utf-8"?> 
     <rdf:RDFServiceLocator  locatorId="services1" 
      xmlns:mx="http://www.adobe.com/2006/mxml"
      xmlns:rdf="com.ibm.rdf.mvc.rpc.service.locator.*" 
      xmlns:service="com.ibm.rdf.mvc.rpc.service.mxml.*" > 
     <service:RDFService  id="employeeServiceByXMLRPC" protocol="XML" 
     targetURL="rdfxmlrpc" destination="employeeService"/> 
     </rdf:RDFServiceLocator> 

    注意,这个 Servicelocator 是由 locatorId 标识的,这个标识符是唯一的,通过这个标识符可以唯一的确定一个 Servicelocator,这样就允许在 RDF Application 中定义多个 Servicelocator。

    这个 Servicelocator 中有一个 service,由 id 属性标识,targetURL 表明调用这个服务将会请求这个 url,在本我们的员工管理系统中,就是在 web.xml 文件中配置的那个 servlet,destination 标识后台发布出来的服务 id。配置 RDF_MVC.mxml

    在其中加入:

    <business:XMLServices id="employeeService1" />

    <control:RemoteControl id="employeeController"/>

    实例化 Servicelocator 和前端控制器。

  • 运行

    运行 RDF_MVC.mxml,如下图所示 :



    图 8. 结果(查看大图
    图 8. 结果

  • 结束语

    本文对基于 Cairngorm 的 MVC 框架的扩展以及如何应用作了一个详细的介绍,当然还有一些细节问题值得研究,相信通过本文的示例项目,可以帮助您更快的掌握这种全新的开发方式。


    下载

举报
IBMdW
发帖于7年前 0回/1K+阅
顶部