一点点感想:写程序都快十年里,几乎没有用过什么大型的框架,实为忏愧,其实对于设计模式还是耳熟能详,mvc,facade,factory模式等等,其实在自己代码中都会用到,不过都是自己写的,没有框架那么复杂,但可定是符合实际设计需要的。MVC设计模式主要是解决了代码耦合性的问题,当为此给程序带来的理解问题就会多一些了,毕竟顺序思维还是好理解一点。由于项目中原来自己写的MVC框架在代码不断的增加,已经变得难以理清头绪了,尤其是在Flex的时间和回调中交织时,变得由为困难。遂决定用一个开源的MVC框架来理清我的问题。由于需要设计的是一个需要高效运行的设计器,而且设计器中的元素会需要不断的加载和删除,使用Robotlegs框架就有点困难,他是通过监听舞台元素添加事件实现消息注入的,对设计器而言,需要不断地加载和删除就会产生效率问题,最后回归到了puremvc框架上了。其实,知道MVC模式和可以使用,并用好MVC模式写代码,还是需要一段时间的。MVC模式如果简单的理解就是通过将模型,视图用控制器分开来实现模型和视图的松耦合,那么如何实现的呢?有应该以什么样的方式来写自己的代码,并真正实现MVC松耦合的目的呢?本人通过自己的理解,来学习puremvc框架,以解决上述问题,本文的思路也是通过带着问题的方式来逐步学习的。
初始问题:
- MVC的三部分在puremvc中是如何对应的?
- puremvc是如何实现松耦合的?
- puremvc是如何来构建代码的?
本文通过puremvc的官方教程《puremvc实现术语阐述及最佳实践》一书和Login的例程来学习的,笔者也是刚学习,希望可以一同探讨。阅读本文需要Flex的基本知识和MVC基本概念,还有一颗对知识无线追求的心。
首先,我们通过书本上的知识来梳理一下MVC如何在puremvc中的体现。
如上图所示,MVC三部分通过消息总线(MVC内部必须实现),来实现模型与视图的分离,并通过控制器实现了业务逻辑的处理。对于常见的场景就是加载数据,用户修改数据,再保存数据,在MVC模式下,加载数据有用户在View上发起,通过Command交给Controller,Controller调用Model取得数据,当数据取得后,发送通知消息到消息总线,相应的View会接受消息,并完成数据的显示,用户在View上修改后,通过Controller再将数据写回Model。如此就是一个MVC模式下普通的工作场景,这样的好处是:View和Model相互的关联性很少,View需要依据自己的需求呈现数据即可,Model实现自己的数据加载和存储,如果需要修改View的代码,Model部分就不需要修改,保持了代码的局部稳定性。另外,消息总线很好地实现了一对多的消息传递,可以实现多个视图同时更新的需求。以上是MVC的简单模式,在puremvc中,就具体化多了。
消息总线使用Notification对象传递,Controller通过Command进行处理,机制与前面讲述的一样,只是View有Mediator(中介),Model由Proxy(代理)处理具体的逻辑。这里就回答了puremvc是如何实现MVC模型的问题了。
下面通过Login的具体例子来分析puremvc如何使用,以及他实现松耦合的。
首先,如果需要下载代码请下载Github上的代码,不过笔者发现Flash Builder4.6无法编译,所以自己新建了一个工程PureMVCLogin的项目,如果有需要,我可以传到空间中。
为了理清Login程序的脉络,我绘制了类图,并表示了程序的初始化路径(蓝色关联线条)。
再来一张时序图,讲述了Login各模块的工作顺序(可放大查看)。
Login的前期初始化顺序就是从创建Facade开始的,Facade为单例模式,通过ApplicationFacade.getInstance()建立Facade实例,完成实例化后偶,puremvc已经可以开始内核就初始化完成了。下一步就是建立应用系统的相关工作作了。ApplicationFacade代码如下:
public class ApplicationFacade extends Facade { /** * Notification name constants * */ public static const APP_STARTUP: String = "appStartup"; public static const LOGIN: String = "login"; /** * Singleton ApplicationFacade Factory Method */ public static function getInstance(): ApplicationFacade { if (instance == null) instance = new ApplicationFacade(); return instance as ApplicationFacade; } /** * Register Commands with the Controller */ override protected function initializeController() : void { super.initializeController(); // // register commands registerCommand( APP_STARTUP, ApplicationStartupCommand ); registerCommand( LOGIN, LoginCommand ); } public function startup(app: PureMVCLogin):void { this.sendNotification( ApplicationFacade.APP_STARTUP, app ); } }
在Application中开始取得facade的实例后,在Application的createComplete事件中执行facade的startup函数,该函数发送APP_STARTUP通知,并带有app对象,app对象保护view相关的应用(在初始化后面的Mediator时需要),由于在facade在initializeController中注册了APP_STARTUP和LOGIN命令,在startup调用开始是,puremvc内核已经可以工作了,接下来的事情就是初始化与具体应用相关的任务了。
ApplicationStartupCommand为宏命令,即就有多个命令集合的对象,在Login中,他执行了ModelPrepCommand和ViewPrepCommand两个命令,需要注意的是再宏命令中的命令是按顺序执行的,即先初始化模型,在初始化视图,并且,视图初始化的时候,会通过facade取得相应的Proxy(代理)引用,因此是有顺序要求的。模型的初始化任务就是建立并注册相应的Proxy到框架中,视图的初始化任务就是建立总的Mediator(中介)ApplicationMediator,并在其中建立并注册Mediator到框架中。这样,Login系统的mvc模式就初始化完了。
需要说明的就是Mediator与View之间是通过Flex的Event事件来传递信息的,具体编程模式请仔细看前面的官方教程。本例中LoginPanelMediator注册了LoginPanel的登陆按钮事件,即Mediator接管具体界面的事件,再把事件建立好可以被其他模块处理的对象,并推送给框架(sendNotification),此时Mediator的任务已经完成,就只需等待登陆成功或失败的消息了。框架接受到Notification事件后,会找到监听了该通知的Mediator(一般用于多个中介间通讯)或Command(与代理通讯)。Login例程中,LOGIN时间ApplicationFacade初始化的时候注册了相应的命令,此时框架就会把命令对象创建,并调用Commande的execute方法,LoginCommand代码如下:
override public function execute(note: INotification) :void { var loginVO: LoginVO = note.getBody() as LoginVO; var loginProxy: LoginProxy = facade.retrieveProxy(LoginProxy.NAME) as LoginProxy; loginProxy.getUser(loginVO); }
在上面的代码中,通过名称找到对应的Proxy对象,并执行登陆的业务逻辑,即调用getUser方法。这里可以看出Controller对Proxy的控制是接口关联的,对于View而言不需要知道登陆如何处理,对Controller而言,他需要知道由哪个命令来处理,命令需要知道如何与具体的代理通讯,实现登陆命令。这里并没有方法返回,如果命令执行成功或失败,就Proxy来发送具体的Notification,Proxy也不需要担心结果由谁来处理,它只需要知道如何取得数据,并发送通知即可,通知总线会找到相应的执行体来执行,如果没有也不是发送者需要担心的。
// // populate its data object using the result var loginVO: LoginVO = event.result as LoginVO loginVO.loginDate = new Date(); setData( loginVO ); // // change the view state _appProxy.viewState = ApplicationProxy.LOGGED_IN_STATE; // // notify all interested members sendNotification( LoginProxy.LOGIN_SUCCESS );
在Login程序中,当从服务端成功取得登陆后,LoginProxy会通知ApplicationProxy修改他的viewState,并发送一个登陆成功的通知。该通在LoggedInBoxMediator的类中已经通过listNotificationInterests方法订阅了改通知,这样改通知就会由LoggedInBoxMediator处理,并最终体现在其关联的LoggedInBox界面上,如果登陆失败,方法与此相同,请仔细看前面时序图的下半部分。
此时,登陆的功能已经在mvc模式的驱动下合作完成了。那么为什么要这么复杂的处理呢?通过Notification在View(Mediator)和Model(Proxy)之间流动,View和Model就完全解耦了,只要Model保持稳定的接口(通知对象),那么重写View部分是,是不需要修改Model的代码的。消息订阅模式解决了需要思考负载事件传递的烦恼。比如在Flex开发中,我们会用到很多的模块(View),如果各模块之间需要通讯,那么一般的写法就是相互添加Listener或通过callback方式来传递数据,如果相关模块数量很多的情况下,这些传递数据的方式就麻烦了,各模块修改自己的方法时还需要考虑其他模块是否会有问题(笔者在写设计器的过程中深受其害),如果应用puremvc,我会把各模块通过Notification建立好通讯机制,从而不用当心其他模块的实现问题了。比如在A模块修改了一个对象,此时,B,C模块就可以通过监听通知来改变自己的对象,作为A模块,至需要考虑发出通知,其他的就不需要考虑了,这就是消息总线(Notification)的好处了。
此时第二个问题:puremvc是如何实现松耦合的就好回答了吧,通过Notification机制实现Model和View的松耦合。第三个问题:puremvc是如何来构建代码的通过顺序图已经很好的说明了。
下面请思考:为什么存在直接注册Notification订阅的方法和registerCommand的订阅方法?
.........
笔者看到Login事件通过Command实现,而LOGIN_SUCCESS和LOGIN_FAILED是通过listNotificationInterests方法订阅的,为什么呢?仔细阅读了官方教程后找到了答案?
Notification可以通过上述两种方式进行订阅,而一般情况下,需要复杂业务逻辑处理的使用Command处理,还有就是官方说Proxy发送但不接受Notification,即如果需要Proxy接受Notification是不行的,需要通过Command来执行Proxy的方法实现修改Proxy下面的Model数据。各Mediator可以通过直接订阅Notification的方法实现消息处理。请参阅官方教程中的:Mediator和Proxy之间、Mediator和其他Mediator之间的耦合。
本文就Login的示例代码深入学习了puremvc在ActionScript中的应用,笔者也是在学习中,希望可以共同学习和交流。
版权声明:本文为博主原创文章,未经博主允许不得转载。