它与 pureMVC 一样,都是使用 MVC 思想搭建结构的一套框架,他们的最终目的都是使程序松耦合。而它又与 pureMVC 不同
首先他没有像pureMVC那样四处开花,使用event来传递信息的RobotLegs决定了它只能存在在AS平台上;
其次它使用了一个叫做依赖注入的东西,听起来好像比较深奥,事实上就是多使用了一个[Inject]元标签,我们在程序中总是通过这个标签定义的变量来获取某个View component、某个Model等等的引用。而在pureMVC中,我们如果要获得某个引用,一般是使用façade的retrieveMediator(mediatorName)、retrieveProxy(proxyName)这样的方法。
最后,使用注入机制的RobotLegs不需要你重新去改写你的类,不像pureMVC那样要求你所有的类继承它提供给你的类。
这些只是我本人看了一两天所感受到的,如果有理解不周全的地方还请指正。。。
下面是我尝试使用 RobotLegs 做的一个简单的留言本 demo 。我会在后面介绍整个 demo 的运作流程(黄字部分
首先简单介绍一下RobotLegs的组成成员,见下图
Context:这是RobotLegs的总线,差不多相当于pureMVC的façade的概念吧,整个框架的初始化就是从它开始。
View :这部分是你原先就写好的类,他们可能有着自己的事件侦听和简单逻辑——比如 demo 中的隐藏 / 显示历史记录按钮。你需要在 context 的 startup 函数中把你的 view 与 Mediator 相关联,让它作为视图组件的经纪人,管理与其他 actor 的交流。因此Mediator既可以发送事件到RobotLegs的事件总线(比如:有人按了获取数据按钮了,谁负责加载数据的赶紧的),也可以从总线中侦听事件(比如:管数据的说他的数据已经就绪了,那我直接拿来显示了)。
其实在严格的 MVC 模式下,是不能出现第二种情况的,正确的方式是:管数据的说他数据已经就绪了, RobotLegs 将自动执行一条 Command ,由这 条 Command 负责把数据交给 Mediator 。也就是说 View 和 Model 之间的沟通必须经过 Controller 。 但由于 RobotLegs 中,我们无法在 Command 内部轻易获得 view 的引用,所以在 Command 里无法直接对 view 进行操作,折衷的办法可以让 Com mand 再广播一条事件,让 Mediator 侦听来自 command 的事件并对 view 进行操作。Model和Service:它们是处在程序底层的,前者用于长久地保存数据,后者用于与外界沟通解析数据提供给Model。当然你可以使用传统MVC模式,假如舍弃Service,并且没有remoteproxy,你的Model不得不得自己去与外界沟通。Model和Service都应当只能广播事件,而不应该去侦听事件,这样可以保持数据的独立性,即使另一个应用程序,只要他使用同样的数据,那么我的类就可以直接拿过去使用。
Controller :前面已经提到, RobotLegs 中的 Controller 就是一条条 Command ,只需要我们事先在 context而显示历史记录的流程其实大同小异,首先由service类从数据库获取数据,将php页传送来的JSON解析成array+object,然后广播一条解析完毕的事件,该事件对应的Command会自动将解析完毕的结果添加到Model中,Model在完成数据更新后发送一条更新完毕的事件,由输出框来显示它的内容。
件发生时这条命令就自动执行,执行完成后自动消失。
首先我们来看一下我们的context类:
package yuyuef
{
//省略import语句和构造函数等等
publicclass AppContext extends Context
{
overridepublicfunction startup():void
{
//映射mediator
this.mediatorMap.mapView(InputArea,InputAreaMediator);
this.mediatorMap.mapView(OutputArea,OutputAreaMediator);
this.mediatorMap.mapView(HistroyArea,HistroyAreaMediator);
//映射command
this.commandMap.mapEvent(ChatEvent.SUBMIT_MESSAGE,SubmitCommand,ChatEvent,false);
this.commandMap.mapEvent(ChatEvent.DB_DATA_NEEDED,LoadMessageCommand,ChatEvent,false);
this.commandMap.mapEvent(ChatEvent.DB_DATA_RECEIVED,ParseMessageCommand,ChatEvent,false);
//映射model单例
this.injector.mapSingleton(CurrentMessageModel);
this.injector.mapSingleton(HistroyMessageModel);
//映射service单例
this.injector.mapSingleton(AppServies);
}
}
}
我们覆盖了startup方法,这个方法会在程序启动时自动执行,所以在它内部进行一些对应关系的映射。我这里一共分成MVCS4个步骤,将我自定义的MXML组件与mediator相关联、将Command与Event相关联、注入model、注入service。
然后在Main.mxml中引入这个context
<fx:Declarations>
<!-- 将非可视元素(例如服务、值对象)放在此处 -->
<yuyuef:AppContext contextView="{this}" />
</fx:Declarations>
接下来是Mediator(中介器)类,我这个demo分成了3个部分:输入组件(包括输入框、颜色拾取器、提交按钮)、输入组件(即一个TextArea)、历史记录组件(包括按钮和一个TextArea)。我们举输入组件的中介器为例来看看它的源码:
package yuyuef.view.mediator
{
//各种省略....
publicclass InputAreaMediator extends Mediator
{
[Inject]
publicvar inputArea:InputArea;
overridepublicfunction onRegister():void
{
this.addContextListener(ChatEvent.UPDATE_MESSAGE,onMessageUpdated);
//错误的写法,直接注册侦听器到子组件,越权
//this.eventMap.mapListener(inputArea.submitButton,MouseEvent.CLICK,submitMessage);
this.eventMap.mapListener(inputArea,UserInputEvent.INPUT_DONE,submitMessage);//正确,注册经过父组件过滤后发出的事件
}
privatefunctiononMessageUpdated(e:ChatEvent):void
{
//错误写法,访问了inputArea的子组件,越权
//inputArea.input.text = '';
//inputArea.input.setFocus();
inputArea.clearInput();//正确,通过调用顶级组件的API来改变子组件
}
privatefunctionsubmitMessage(e:UserInputEvent):void
{
var evt:ChatEvent = newChatEvent(ChatEvent.SUBMIT_MESSAGE,inputArea.message);
trace('dispatch:'+evt);
this.dispatch(evt);
} }
}
我们在onRegist函数中注册了这个组件可能关心的事件的侦听:点击提交事件和更新信息事件。
可以看到,从 RobotLegs 事件总线中侦听事件我采用了 addContextListener 方法,而侦听鼠标点击按钮事件我采用了 this .eventMap.mapListener , eventMap 其实就是一个映射器,他可以将一个事件侦听添加到一个对象上,由于我这个 Mediator 是 [颜色选择器+输入框+发送按钮] 3 者之和而不单单是 [发送按钮] 的中介器,这是一个多个子组件复合而成的复杂组件,我们想侦听其中的某个组件的事件,可以用下面几个办法添加侦听:
this.eventMap.mapListener(inputArea.submitButton,MouseEvent.CLICK,submitMessage);
inputArea.submitButton.addEventListener(MouseEvent.CLICK,submitMessage);
等等,但是这样就破坏了复合组件的完整性。在 Mediator 中,我们不应该访问对应 View 的子组件。因此需要在 View 中对它的子组件进行侦听,然后再发出一个自定义事件,再在 mediator 中对自定义事件进行侦听。
当我们在inputText中输入一些字,并点击发送时,InputAreaMediator首先侦听到CLICK事件,立马广播了一个ChatEvent.SUBMIT_MESSAGE,并且让这条事件携带了用户在它里面输入的message,告诉程序用户需要提交一条这样的message。
this.dispatch(evt)方法是robotlegs框架成员(Actor)都具有的一个方法,专门用来向事件总线广播事件
接下来看看SubmitCommand的源码:
package yuyuef.control
{
//省略省略省略....
publicclass SubmitCommand extends Command
{
[Inject]
publicvar evt:ChatEvent;
[Inject]
publicvar currentMessage:CurrentMessageModel;
[Inject]
publicvar servies:AppServies;
overridepublicfunction execute():void
{
servies.saveMessage(evt.infoasChatVO);
currentMessage.appendText(evt.infoasChatVO);
}
}
}刚刚输入框的中介器向事件总线中广播了一条ChatEvent.SUBMIT_MESSAGE事件,由于该事件在context里与SubmitCommand关联过,robotLegs在发现该事件后立刻实例化了一个SubmitCommand并执行它的execute函数,即让service去向服务器saveMessage(),同时让currentMessage这个Model更新它的内容。
可以看到,这条command里的event、service、currentMessage这3个对象都是通过[Inject]注入的,它可以保证我获取到需要的类的实例(这里除了event都是单例的),并进行操作。这个行为有点类似pureMVC的façade.retrieveProxy(name)方法来获取某个对象。
接下来,service在command的控制下访问php,由php连接数据库,尝试保存这条信息,假如成功,php页会返回result=saved,告诉AS保存成功。
下面的我用到的服务器端php代码:
<?php
error_reporting(0);
include_once('dbconnect_function.php');
$action= $_POST['action'];
if('save'== $action){
$body = $_POST['message'];
$time = $_POST['time'];
$color = $_POST['color'];
$sql = "INSERT INTO message (body,time,color) VALUES('$body','$time','$color')";
$result = yuyuef_mysql_query($sql);
if($result){
echo 'result=saved';
}else{
echo 'result=unsaved';
}
exit();
}
if('load'== $action){
$sql = 'SELECT * FROM message';
$result = yuyuef_mysql_query($sql);
$result = db_result_to_array($result);
echo json_encode($result);
exit();
}
?>
这段代码先判断 FLASH 的行为,是要保存还是读取消息并执行对应的操作。
在service向服务器保存数据的同时,currentMessageModel同时会更新自己的数据,将这段新的消息添加到自身,然后广播一个事件:“我的数据已经被更新了,谁关心的就拿去用”,这个事件又被输入框中介器(InputAreaMediator)和输出框中介器(OutputAreaMediator)所听到,输入框知道自己原先的消息已经进入程序了,就将自己的InputText清空;而输出框则更加关心这个事件所携带的消息,即ChatEvent.info as TextFlow,它会将这个文本流赋予给自己的textFlow属性(spark组件中的textArea和textInput均支持新的TLF框架)用于刷新自己的显示。
到此就是“用户输入留言——该留言保存入数据库——这条留言显示在输出框的”流程
而显示历史记录的流程其实大同小异,首先由service类从数据库获取数据,将php页传送来的JSON解析成array+object,然后广播一条解析完毕的事件,该事件对应的Command会自动将解析完毕的结果添加到Model中,Model在完成数据更新后发送一条更新完毕的事件,由输出框来显示它的内容。