作者:吴秦
参考资料
[1] PureMVC官方网站:www.puremvc.org
[2] Wikipedia:http://zh.wikipedia.org/zh-cn/MVC
[3] 《PureMVC_Implementation_Idioms_and_Best_Practices_cn》
PureMVC(AS3)剖析:开篇
2012-12-29 00:24 by 吴秦, 5768 阅读, 8收藏, 编辑
PureMVC(AS3)剖析:开篇
缘起 |
自从事flash webgame开发起,3个项目都使用到了MVC模式:1个自己构建的MVC没有使用外部框架;2个使用的PureMVC框架。对PureMVC也有了一定的深度的认识,是时候来总结、吐槽下。现在网上已经流传很多关于PureMVC的资源,但是总觉得深度不够,故有了现在这个系列,我尽量带着自己的思考深入的介绍PureMVC,同时也能引起大家的思考。这里我并不是一味的说PureMVC有多好,它也有值得质疑和使用不爽的地方。
MVC思维
要了解PureMVC框架,首先得有MVC的思维方式,用MVC的思想去思考问题,分解需求、实现需求。MVC背后的核心思想是:
代码重用(code reusability)、关注点分离(separation of concerns,SoC)
MVC思维围绕这两点转,下面举个例子说明。对于一个游戏或软件来说,给人的第一感觉就是界面(视图View),它展示了一些数据等信息给用户。这些界面上的数据可以存储在视图View属性或定义的变量中,如游戏中用户信息栏中显示玩家的等级、经验、昵称:
图:玩家信息栏界面
这样数据跟视图绑定在一起,其它界面视图(玩家详细信息栏)想要等级、经验、昵称等数据需要访问玩家信息栏界面,又或者从后台重新拉取。
图:玩家详细信息界面
这里违反了“代码重用和关注点分离”思想,需要把数据独立出来保存在一个与View无关地方模型(Model),多个视图可以共用一份数据。这样代码即可重用,关注点也实现了分离, View只需要关注如何展示信息, Model只需要关注数据本身及相关逻辑。
上面达到了界面和数据分离之后,但是思考这样一个过程:用户操作了View需要更新Model,同理Model改变了需要更新View的显示,该如何去做?可能会想到用户操作View时直接调用Model接口去更新数据,这时其他共用这个Model的视图View如何同步更新又是一个问题。Model改变了数据,直接调用View接口更新显示的话,它必须要知道所有的共用这个模型Model的视图View。
这样视图View与模型Model就不能达到彻底的“代码重用和关注点分离”。MVC的指导思想就是把这个部分代码独立出来称为控制器Controller。模型和视图从此也只关心控制器,而不关心对方。他们的代码都是处理自己的事情,别人的事情全部交给控制器去办,这使得他们自己的功能也非常独立,减少了需要考虑的要素。
在这之后,三者的关系只存在简单的调用代码。那么为了能够彻底的分离和解耦,就可以将调用代码改为事件或者其他的动态形式(如发布/订阅、依赖注入等等),常用的MVC框架都实现了这样的功能。
图:MVC结构图(实线——>表示依赖;虚线---->表示事件/通知等)
l 模型(Model):“数据模型”(Model)用于封装与应用程序的业务逻辑相关的数据以及对数据的处理方法。“模型”有对数据直接访问的权力,例如对数据库的访问。“模型”不依赖“视图”和“控制器”,也就是说,模型不关心它会被如何显示或是如何被操作。但是模型中数据的变化一般会通过一种刷新机制被公布。为了实现这种机制,那些用于监视此模型的视图必须事先在此模型上注册,从而,视图可以了解在数据模型上发生的改变。(比较:观察者模式(软件设计模式))
l 视图(View):视图层能够实现数据有目的的显示(理论上,这不是必需的)。在视图中一般没有程序上的逻辑。为了实现视图上的刷新功能,视图需要访问它监视的数据模型(Model),因此应该事先在被它监视的数据那里注册。
l 控制器(Controller):控制器起到不同层面间的组织作用,用于控制应用程序的流程。它处理事件并作出响应。“事件”包括用户的行为和数据模型上的改变
请大家记住“MVC思维“及上面这些描述,后面还会引用这些。
PureMVC框架
PureMVC框架的目标很明确,即把程序分为低耦合的三层:Model、View和Controller。降低模块间的耦合性,各模块如何结合在一起工作对于创建易扩展,易维护的应用程序是非常重要的。
在PureMVC实现的经典MVC元设计模式中,这三部分由三个单例模式类管理,分别是Model 、View和Controller。三者合称为核心层或核心角色。PureMVC中还有另外一个单例模式类——Façade,Façade提供了与核心层通信的唯一接口,以简化开发复杂度。
图: PureMVC设计示意图(摘自官方网站)
从设计图中可以清楚看到,除了核心层、Façade之外,还有Mediator、Proxy、Command。
l Proxy: Model 保存对 Proxy 对象的引用,Proxy 负责操作数据模型,与远程服务通信存取数据。
l Mediator: View 保存对 Mediator 对象的引用。由 Mediator 对象来操作具体的视图组件,包括:添加事件监听器,发送或接收 Notification ,直接改变视图组件的状态。这样做实现了把视图和控制它的逻辑分离开来。
l Command: Controller 保存所有 Command 的映射。Command 类是无状态的,只在需要时才被创建。Command 可以获取 Proxy 对象并与之交互,发送 Notification,执行其他的 Command。
为了彻底解耦,避免直接的函数调用,PureMVC使用观察者模式(发布/订阅)的形式传递消息。PureMVC 的通信并不采用 Flash 的 EventDispatcher/Event,因为PureMVC 可能运行在没有 Flash Event 和 EventDispatcher 类的环境中,它的通信是使用观察者模式以一种松耦合的方式来实现的。
你可以不用关心 PureMVC 的 Observer/Notification 机制是怎么实现的,它已经在框架内部实现了。你只需要使用一个非常简单的方法从 Proxy, Mediator, Command 和 Facade 发送 Notification,甚至不需要创建一个Notification 实例。
下面是我对PureMVC的理解画的设计示意图:
图: PureMVC另种示意图
PureMVC(AS3)剖析:实例
2013-01-29 12:34 by 吴秦, 8790 阅读, 18收藏, 编辑
PureMVC(AS3)剖析:实例
实例 |
上篇介绍了MVC的思维方式“代码重用(code reusability)、关注点分离(separation of concerns,SoC)”,并介绍了PureMVC框架的设计。本篇从一个实例出发,详细介绍PureMVC框架中的元素、推荐的项目目录组织方式、代码格式等等。
1. PureMVC模块划分
上篇中介绍了PureMVC框架设计中存在的角色,这里先回顾一下:经典MVC元设计模式中的三部分由三个单例类管理,分别是Model 、View和Controller。PureMVC中还有另外一个单例类——Façade,Façade提供了与核心层通信的唯一接口。这4个单例类构件了PureMVC的骨架。
实际上,我们知道一个游戏或项目由多个模块组成(如登陆/注册模块、主场景模块、关系链模块等等),每个模块通常都是单独的Model、View、Controller。PureMVC的那4个单例类是满足不了这样的需求的,但是它提供了Proxy、Mediator、Command来解决这个问题。
图1:PureMVC项目的模块划分
Proxy、Mediator、Command分别对应MVC中的Model、View、Controller,也分别有对应的单例管理Model保存所有的Proxy引用、View保存所有的Mediator引用、Controller保存所有的Command映射:
l Proxy: Proxy 负责操作数据模型,与远程服务通信存取数据;
l Mediator: Mediator操作具体的视图组件UI,包括:添加事件监听器,发送或接收 Notification ,直接改变视图组件的状态;
l Command: Command 可以获取 Proxy 对象并与之交互,发送 Notification,执行其他的 Command。
2. 推荐PureMVC初始化流程
PureMVC框架的入口是继承Façade的子类:ApplicationFacade(这个随你喜欢,startUp()方法启动初始化框架。
ApplicationFacade类: |
public class ApplicationFacade extends Facade implements IFacade { private static const STARTUP:String = "startup"; public static function getInstance():ApplicationFacade { if (instance == null) instance = new ApplicationFacade(); return instance as ApplicationFacade; } override protected function initializeController():void { super.initializeController(); registerCommand(STARTUP, StartupCommand); } public function startUp(rootView:DisplayObjectContainer):void { sendNotification(STARTUP, rootView); removeCommand(STARTUP); //PureMVC初始化完成,注销STARUP命令 } |
这个类有几点需要说明的:
n 它是PureMVC应用程序的入口。ApplicationFacade 类对象负责初始化Controller(控制器),建立Command与Notification 名之间的映射。
n ApplicationFacade 类仅定义Notification(通知)常量:STARTUP (private),标识应用程序启动,其它Notification(通知)常量抽离到ApplicationConstants中定义,这样更简洁、清晰。
n 为了使ApplicationFacade结构更清晰,简洁。将注册Command、Proxy、View&Mediator的工作抽离到BootstrapCommands、BootstrapModels、BootstrapViewMediators去做。
l BootstrapCommands:初始化应用程序事件与Command之间的映射关系;
l BootstrapModels:Model 初始化,初始化应用程序启动过程中需要用到的Proxy,并注册;
l BootstrapViewMediators:View 初始化,唯一创建并注册ApplicationMediator,它包含其他所有View Component并在启动时创建它们。
调用startUp()启动应用程序,发送STARTUP命令;然后触发StartupCommand,它包含三个子command执行(这里借鉴Robotlegs的思想,将Command、Model、ViewMediator初始化工作分离,使得程序结构更清晰。)
图2: StartupCommand包含3个子命令BootstrapCommands、BootstrapModels、BootstrapViewMediators
简而言之,框架初始化流程可以表示如下:
图3: PureMVC应用程序框架初始化流程
3. 推荐PureMVC结构
图4:推荐目录结构(图为连连看例子的目录结构)
推荐目录结构为,按MVC分为3个目录:model、view、controller。Model下面有定义vo的子目录,view下面有定义UI界面的子目录ui等,controller下面有定义初始化的子命令目录boostraps。实际项目中的基本上每个功能模块,在三个目录下对应的类。
4. PureMVC模块间通信
当一个模块需要与其它模块交互时,可以通过发送/接收Notification或者通过façade的facade.retrieveMediator、facade.retrieveProxy检索到指定模块,然后调用相应接口。
Flash事件和PureMVC通知的主要差异是:事件遵循“责任链”模式,在显示层级中“冒泡”直到有父组件处理它;而通知遵循“发布/订阅”模式。使用通知进行通信,PureMVC各模块之间不需要建立父子关系。
通知并不是事件的替代物。一般情况下,Mediator给其视图组件添加事件侦听器,按常用方式处理,然后给目标Command广播Notice,或与其他Mediator通信。Proxy通过广播Notice,与Command实例和Mediator通信。
推荐使用Notification机制,但是全部使用Notification这种强松耦合模式:①强松耦合加重通信次数;②带反馈数据的通信加重通信负担。适当使用直接通信方式。
图5:PureMVC之间的通信
5. PureMVC实例:连连看游戏
首先声明“连连看”游戏并非很适合使用PureMVC框架,因为它本身并不复杂,而且没有需要复用的逻辑代码等。这里通过这样一个不太复杂的小游戏,介绍PureMVC。
需求
《连连看》是一款操作非常简单的小游戏,它的玩法就是用直线将两个相同的图标消掉,分为十关,难度递进。
【概要】玩家可以将 2 个相同图案的对子连接起来,连接线不多于 3 根直线,就可以成功将对子消除。
【操作】第一次使用鼠标点击棋盘中的棋子,该棋子此时为“被选中”,以特殊方式显示;再次以鼠标点击其他棋子,若该棋子与被选中的棋子图案相同,且把第一个棋子到第二个棋子连起来,中间的直线不超过 3 根,则消掉这一对棋子,否则第一颗棋子恢复成未被选中状态,而第二颗棋子变成被选中状态
设计
整个游戏分为2个模块:关卡选择模块、关卡具体模块。关卡选择模块,类似一个菜单呈现10个关卡供玩家选择;关卡具体模块,呈现特定关卡的详细信息,如需要消除的棋子、总时间、重排棋子按钮、获取提示按钮、暂停按钮等等。
关卡数据的几点说明:
n 关卡数据可以通过关卡编辑器生成配置文件,然后读取配置文件即可。这样关卡更可控,且可以摆放成各种形状(心形、回形等等)的数据。
n 如果做成网络版的,关卡数据也可以通过服务器端返回。
本实例为了简单,关卡数据根据等级随机生成10种图片,数量固定为40个。
关卡难度递进设定:
n 第一,从时间递减来增加难度,30 * (11 - level)。
n 第二,从可以重新摆放棋子、获取提示次数来体现。
n 如果作为一个商用连连看,还可以通过棋子的种类数量、定时随机重排增加难度。
判断连通算法,可以使用多种:分支界定判断、广度优先搜索法、四个方向的A*算法等等,例子不考虑性能简单的使用了分支界定判断,而且这里也不会深入介绍,毕竟我的目的不是介绍连连看算法,有兴趣的自行了解。
实现
详细代码我已放到GitHub:https://github.com/saylorzhu/linkupGame,自行前往查看。我们做技术的,看代码就可以了解如何使用PureMVC了,看到这里更推荐你去看代码。下面我只介绍代码的几个PureMVC实现的关键地方。
注意点1:PureMVC框架初始化流程,及代码组织
这也是我想推送给大家的,框架初始化流程可参见【2. 推荐PureMVC初始化流程】,代码组织方法可参见【3. 推荐PureMVC结构】。
注意点2:模块划分与通信
游戏分为2个模块:关卡选择模块、关卡具体模块。
n 关卡选择模块包括:
该模块不用存储数据,没有Proxy。
ChooseLevCanvas、ChooseLevCanvasMediator组成View,ChooseLevCanvas负责具体UI展示和操作响应,ChooseLevCanvasMediator负责将来自用户操作的事件转发给PureMVC框架并将来自框架的事件转发给ChooseLevCanvas。
ChooseLevCommand由玩家选择具体关卡事件ApplicationConstants.SELECT_LEVEL触发,该Command调用LevelProxy的接口生成关卡数据,然后通知到“关卡具体模块”开始游戏。
n 关卡具体模块包括:
LevelProxy 属于Model,生成、存储、重排具体关卡的数据,负责数据相关操作。
LevelCanvas与LevelCanvasMediator组成View,LevelCanvas负责具体UI展示和响应玩家操作,LevelCanvasMediator负责将来自用户操作的事件转发给PureMVC框架并将来自框架的事件转发给LevelCanvas。
ReSortCommand由玩家点击重排按钮触发,该Command调用LevelProxy的接口重排关卡中的棋子。
这里涉及到的通信方式有,通知、flash内置事件(xxxCanva与xxxCanvasMediator)。
Command需要侦听通知,需要在framework.controller.boostraps.BootstrapCommands中使用registerCommand注册;
xxxCanvasMediator需要侦听通知,需要在对应Mediator中使用listNotificationInterests注册,并重写handleNotification处理。
模块间通信可参见【4.PureMVC模块见通信】。
注意点3:Command与Proxy、Mediator
Command管理应用程序的 Business Logic(业务逻辑),要协调Model 与视图状态。
Model 通过使用 Proxy 来保证数据的完整性、一致性。Proxy 集中程序的Domain Logic(域逻辑),并对外公布操作数据对象的API。它封装了所有对数据模型的操作,不管数据是客户端还是服务器端的。
Mediator 和Proxy 可以提供一些操作接口让Command 调用来管理View Component 和Data Object ,同时对 Command隐藏具体操作的细节。
注意点4:一般一个Mediator(handleNotification方法)处理的Notification应该在4、5个之内。
还要注意的是,Mediator的职责应该要细分。如果处理的Notification很多,则意味着Mediator需要被拆分,在拆分后的子模块的Mediator里处理要比全部放在一起更好。
注意点5:应该避免Mediator与Proxy 直接交互。
例子项目中遵从了这个规则,但实际上项目Mediator中不可避免需要获取Proxy数据,如果每次都通过一个Notification去获取数据,然后返回数据给Mediator,这样无形中增加了通信次数、带反馈数据的通信加重通信负担。所以可以适当是的在Mediator中facade.retrieveProxy获取Proxy然后拿到数据,而且从proxy直接拿数据,可以保证拿到最新数据。
PureMVC(AS3)剖析:吐槽
2013-02-17 20:57 by 吴秦, 4190 阅读, 0收藏, 编辑
PureMVC(AS3)剖析:吐槽
写在前面 |
世上没有银弹——不存在适用于所有情况的框架,只有适合的框架。再者任何一个好的东西(语言、框架等)最终还取决于用的人,语言和框架本身并不能保证用户的代码清晰、解耦等,当然它只是尽可能地做到这点。所以记住我写这篇不是为了否定PureMVC,相反是为了更好的了解它、使用它。
1. 吐槽一:过于强调解耦
PureMVC引入了多种设计模式、消息机制(使用观察者模式,发布/订阅模式)来解耦各个模块,它确实做到了这点,但是彻底解耦是需要代价的!
1.1. Notification消息命名及管理复杂
PureMVC为了做到跨平台,使用Notification来实现模块间通信,而非Flash原生的EventDispatcher/Event机制。然而Notification使用字符串来定义消息,存在以下“问题”。
注:Notification并不是Event的替代物。一般情况下,Mediator给其视图组件添加Event侦听器,按常用方式处理,然后给目标Command/Mediator广播Notification。
n 消息ID为字符串,虽然字符串可以做到编译时解耦,但无法做到消息强类型,这样错误将推迟到运行时才能发现。
n 消息命名,在一个大型项目中,需要一套详细的规则。相信我,否则你会吃苦头的。特别是多人参与项目中,如果没有按照一定规则命名,命名冲突可是会让你调试一阵。但不管你如何定义命名规则,【记住】为了模块间解耦,Notification发布者应该不关心谁对这个消息感兴趣(谁来处理),感兴趣者自行注册(Mediator通过listNotificationInterests注册、Command通过facade.registerCommand()注册)。例如当Proxy中用户信息改变时,不应该sendNotification通过“UpdateUserInfoVIew”、“UpdateFriendListView”2个通过来分别更新用户信息、好友列表中对应用户的信息,而只是发送一个通知,如“UpdateUserInfo”,用户信息栏、好友列表都注册这个消息,然后分别处理。
n 无法知道Notification的源头。然而这点可以通过在消息体body中,增加字段标识,如:
sendNotification(ApplicationConstants.UPDATE_LEVEL_DATA, { "noticeSource": this, "levelData": m_levelData } ); |
noticeSource标识消息来源,如果您还想要知道消息传递层次,可以用数组表示,顺序插入传递者。
1.2. 强松耦合加重通信次数
PureMVC中模块间通信推荐使用Notification机制,但是全部使用Notification这种强松耦合模式:①强松耦合加重通信次数;②带反馈数据的通信加重通信负担。
图:UI使用Notification修改Proxy中的数据通信过程
PureMVC中UI修改Proxy的数据并返回后刷新过程:❶Mediator收到UI提交事件后,❷发送Notification消息给Command;❸Command进行业务逻辑处理,❹调用Proxy接口修改数据(这里还可能涉及到与服务器通信),❺然后发消息给Mediator刷新,❻Mediator收到消息调用UI接口刷新。
因为都是消息机制,整个流程很长,而且Proxy中对数据进行操作后,发送Notification时,可能需要携带修改后的数据(可能是来自服务器的数据)。这个过程不仅通过次数多,而且带反馈数据的消息增加通信负担。另一方面要调试这个过程,我们只能在编译的时候找出一步一步的通信流程,才能跟踪调试。
2. 吐槽二:解耦增加了代码量,不方便调试
解耦的同时将使项目修改的复杂程度提高,某些解耦的办法还会增加代码量、降低执行效率。PureMVC是一个强解耦的框架,其效率本身不是很高,函数调用层次较深,而有时根本不清楚消息发到了哪里。
PureMVC为了实现解耦增加了代码量,不方便调试,但哪个MVC框架不是呢!这不是PureMVC的问题,已经有前辈编写了PureMVC模版,如FlashDevelop的模板(下载),使用模板可以减少手动编写代码量,但不能减少类的数量。
有篇文章详细介绍了PureMVC的耦合与解耦:耦合与脱耦——深入分析为什么使用pureMVC、接口或抽象基类(入口http://bbs.9ria.com/thread-161667-1-1.html)
3. 吐槽三:过度使用单例模式
单例模式过于万能,属于高耦合写法。PureMVC中有4个单例Model、View、Controller、Façade。我们可以通过Model、View、Controller的getInstance()方法获取实例,并对他们进行操作。然而Façade是用于管理Model、View、Controller并对外提供接口。如果Model、View、Controller对外不可见,为什么要设定为单例,而不是Façade的成员变量呢?
4. 总结
上面说了一些PureMVC的缺点,不过总体来说PureMVC还算一个优秀的框架,解耦彻底、灵活性高。
PureMVC(AS3)剖析:设计模式(一)
2013-03-14 00:56 by 吴秦, 4156 阅读, 2收藏, 编辑
PureMVC(AS3)剖析:设计模式(一)
模式 |
PureMVC框架的目标很明确,即把程序分为低耦合的三层:Model、View和Controller。降低模块间的耦合性,各模块如何结合在一起工作对于创建易扩展,易维护的应用程序是非常重要的。PureMVC框架使用多重设计模式来实现解耦彻底、灵活性。
l 单例(singleton)模式保证一个类仅有一个实例,并提供一个访问它的全局访问点。在PureMVC实现的经典MVC元设计模式中,这三部分由三个单例类管理,分别是Model 、View和Controller。PureMVC中还有另外一个单例类——Façade,为子系统提供统一接口;
l 在PureMVC中模块间使用观察者(Observer)模式通信,以便当一个对象的状态发生改变时,所有依赖它的对象都得到通知并更新。如Proxy中数据更新了,sendNotification通知所有相关显示的地方更新显示;
l 使用外观(Façade)模式为子系统Model、View、Controller接口提供一致对外的界面,定义了一组高层接口,这使得子系统更容易使用;
l 使用中介者(Mediator)模式来封装UI与系统中其他对象的交互,使得各对象不需要显示地互相引用,从而使得其耦合松散,而且可以独立地改变它们之间的交互;
l 使用代理(Proxy)模式为数据对象提供代理以控制数据对象的访问,PureMVC中Proxy负责操作数据模型,与远程服务信存取数据;
l 使用命令(Command)模式将请求封装为一个对象,实现“行为请求者”与“行为实现者”解耦将发出命令的责任和执行命令的责任分割开。
下面详细介绍PureMVC框架中使用的模式。
1. 单例模式
单例(singleton)模式,保证一个类仅有一个实例,并提供一个访问它的全局访问点。它有如下特点:
1) 类只能有一个实例;
2) 它必须自行创建这个实例;
3) 它必须自行向整个系统提供这个实例。
图:单例模式类图
一般语言可以将构造函数置为private,以阻止外部实例化。然而由于AS3中的构造函数必须是public,所以不可以像其它编程语言一样,将构造函数置为private来阻止调用构造函数生成实例。PureMVC中这样实现单例类,例如View单例:
View |
另一种通过保外类实现单例的方式,可以参考这篇文章【游戏中的背景音乐和声效】http://goo.gl/PGPLL。
PureMVC中有4个类都使用了单例模式:Façade(外观模式常使用单例模式)、Model、View、Controller。
2. 外观模式
外观(Façade)模式为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
图:façade模式效果
引入外观角色之后,用户只需要直接与外观角色交互,用户与子系统之间的复杂关系由外观角色来实现,从而降低了系统的耦合度。外观模式特点如下:
l 外观模式为复杂子系统提供了一个简单接口,并不为子系统添加新的功能和行为【注意】。
l 外观模式实现了子系统与客户之间的松耦合关系。
l 外观模式没有封装子系统的类,只是提供了简单的接口。如果应用需要,它并不限制客户使用子系统类。因此可以在系统易用性与通用性之间选择。
l 外观模式注重的是简化接口,它更多的时候是从架构的层次去看整个系统,而并非单个类的层次。
l 外观模式经常使用单例实现,但子系统们可以有多个Façade。
在PureMVC中,为Model、View、Controller类提供了外观类Façade,Façade统一对外提供这3者的接口,使用过程成只需要跟Façade打交道就行。
图:façade与Model、View、Controller类
3. 观察者模式
观察者(Observer)模式(有时又被称为发布/订阅模式),定义对象间的一种一对多依赖关系,使得一个对象状态发送改变时,其相关依赖对象皆得到通知并被自动更新。这里涉及到3个角色:
l 观察者(订阅者):被通知的对象,它需要事先注册对应消息/主题。
l 通知者(发布者):发生改变的对象,当状态发生改变时通知所有依赖它的观察者更新。
l 消息(主题):消息/主题标识观察者感兴趣的内容、通知者状态改变时需要发布的内容。
为了彻底解耦,避免直接的函数调用,PureMVC使用观察者模式(发布/订阅)的形式传递消息。在PureMVC中Mediator、Proxy、Command之间的通信,以通知形式实现松散耦合。Mediator、Proxy、Command都是通知者(发布者)可以调用sendNotification发送消息;Mediator、Command同时也是观察者(订阅者)可以接收来自其它对象的通知。
观察者模式中,维护观察者、通知者、消息/主题之间的映射关系有多种方式:
1) 由通知者维护对应的消息/主题、观察者的映射关系,并在自身状态发送改变时,通知所有的观察者;
2) 由一个管理器维护所有消息/主题、观察者之间的映射关系,当通知者发布消息时,通过管理器调用观察者通知更新。(PureMVC使用这种模式,View是管理器,管理全局的消息映射关系)
图:通知者、观察者、管理器、消息类
3.1. 发布通知
所有的通知者(发布者)都继承自Notifier类,故拥有发布通知的功能,然而Notifier是使用Façade发布通知的。
Notifier |
由前面介绍的外观模式可知façade保存了View的引用,View在观察者模式中充当着管理者的角色,保存着所有消息/主题、观察者的映射。当Mediator/Command/Proxy发布通知时,façade调用View的notifyObservers()方法,遍历保存的映射关系,从而通知所有满足条件的观察者。PureMVC中Mediator/Command/Proxy发布通知的时序如下图所示:
图:发布通知时序
3.2. 注册通知
观察者想接受到相应通知必须先注册,Mediator、Command分别通过以下接口注册:
l listNotificationInterests():Array
l registerCommand( notificationName : String, commandClassRef : Class ) : void
Mediator、Command从注册到接收通知的时序图如下:
图:注册、接收通知时序
下篇将介绍PureMVC中的中介者模式、代理模式、命令模式。
PureMVC(AS3)剖析:设计模式(二)
2013-03-25 14:00 by 吴秦, 2893 阅读, 7收藏, 编辑
PureMVC(AS3)剖析:设计模式(二)
模式 |
上一篇中介绍了PureMVC中使用的3种设计模式:单例模式、观察者模式、外观模式。本篇将继续介绍剩下的3种设计模式:
l 使用中介者(Mediator)模式来封装UI与系统中其他对象的交互,使得各对象不需要显示地互相引用,从而使得其耦合松散,而且可以独立地改变它们之间的交互;
l 使用代理(Proxy)模式为数据对象提供代理以控制数据对象的访问,PureMVC中Proxy负责操作数据模型,与远程服务信存取数据;
l 使用命令(Command)模式将请求封装为一个对象,实现“行为请求者”与“行为实现者”解耦将发出命令的责任和执行命令的责任分割开。
1. 中介者模式
中介者(Mediator)模式:用一个中介对象来封装一系列对象交互。中介者使各对象不需要相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。适用性:(摘自:《设计模式:可复用面向对象软件的基础》)
l 一组对象以定义良好但是复杂的方式进行通信。产生的相互依赖关系结构混乱且难以理解。
l 一个对象引用其他很多对象并且直接与这些对象通信,导致难以复用该对象。
l 想定制一个分布在多个类中的行为,而又不想生成太多的子类。
在PureMVC中,Mediator帮助我们创建或重用已有UI组件,而UI不用知道PureMVC框架相关的东西,UI仅用于显示数据、接收用户输入。Mediator是UI组件与框架的中介,它负责将来自PureMVC框架的消息转接到UI,并将UI的消息(flash事件)转发广播到PureMVC框架。这样通过Mediator解耦了UI与PureMVC框架元素(Proxy、Mediator、Command),而不用互相引用。
图:中介者模式
一个Mediator只与一个UI绑定(1对1),Mediator构造函数参数传递与之绑定的UI。通过façade的registerMediator方法注册Mediator,以接收PureMVC框架的通知(通过上篇介绍的外观模式,可以知道其实最终是通过View这个单例来注册的,façade只是提供接口)。
图:Mediator与UI的关系
2. 代理模式
代理(Proxy)模式:为其它的对象提供一种代理,以控制对这个对象的访问。按照使用目的来划分,代理有以下几种:(摘自:《设计模式:可复用面向对象软件的基础》)
l 远程(Remote)代理:为一个位于不同的地址空间的对象提供一个局域代表对象。这个不同的地址空间可以是在本机器中,也可是在另一台机器中。远程代理又叫做大使(Ambassador)。
l 虚拟(Virtual)代理:根据需要创建一个资源消耗较大的对象,使得此对象只在需要时才会被真正创建。
l Copy-on-Write代理:虚拟代理的一种。把复制(克隆)拖延到只有在客户端需要时,才真正采取行动。
l 保护(Protect or Access)代理:控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限。
l Cache代理:为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。
l 防火墙(Firewall)代理:保护目标,不让恶意用户接近。
l 同步化(Synchronization)代理:使几个用户能够同时使用一个对象而没有冲突。
l 智能引用(Smart Reference)代理:当一个对象被引用时,提供一些额外的操作,比如将对此对象调用的次数记录下来等。
在所有种类的代理模式中,虚拟(Virtual)代理、远程(Remote)代理、智能引用代理(Smart Reference Proxy)和保护(Protect or Access)代理是最为常见的代理模式。
在PureMVC中,Proxy帮助我们以更易于重用、修改对应用程序影响最小的方式暴露数据结构、接口给应用程序。Proxy可能只是简单的管理本地数据对象,以同步方式获取或修改数据;也可能是远程服务器数据,以异步方式操作数据,服务器数据返回之后以Notification方式告诉应用程序。
总之,Proxy 集中程序的Domain Logic(域逻辑),并对外公布操作数据对象的 API。它封装了所有对数据模型的操作,不管数据是客户端还是服务器端的,对程序其他部分来说就是数据的访问是同步还是异步的。
图:Proxy模式
3. 命令模式
命令(Command)模式:又称为行动(Action)模式或交易(Transaction)模式,命令模式把一个请求或者操作封装到一个对象中。命令模式允许系统使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。(摘自:《设计模式:可复用面向对象软件的基础》)
命令模式是对命令的封装,把发出命令的责任和执行命令的责任分割开,委派给不同的对象。每一个命令都是一个操作:请求的一方发出请求要求执行一个操作;接收的一方收到请求,并执行操作。命令模式允许请求的一方和接收的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求是怎么被接收,以及操作是否被执行、何时被执行,以及是怎么被执行的。
图:PureMVC中命令执行时序
在PureMVC中,命令用来检索、操作Proxy,或者与Mediator通信,或者执行其它命令(符合命令)。命令有两种:SimpleCommand、MacroCommand,分别用于执行单个任务、多个任务。MacroCommand可以顺序其实多个SimpleCommand。
图:简单命令与复合命令
命令模式优点:(http://baike.baidu.com/view/1963264.htm)
l 降低对象之间的耦合度。
l 新的命令可以很容易地加入到系统中。
l 可以比较容易地设计一个组合命令。
l 调用同一方法实现不同的功能
命令模式缺点:
使用命令模式可能会导致某些系统有过多的具体命令类。因为针对每一个命令都需要设计一个具体命令类,因此系统可能需要大量具体命令类,这将影响命令模式的使用。