MVC 编程笔记2

转载自http://blog.csdn.net/kenkao/article/details/50291991

作者:吴秦

出处:http://www.cnblogs.com/skynet/

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名吴秦(包含链接).

参考资料

[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结构图(实线——>表示依赖;虚线---->表示事件/通知等)


模型(Model):“数据模型”(Model)用于封装与应用程序的业务逻辑相关的数据以及对数据的处理方法。“模型”有对数据直接访问的权力,例如对数据库的访问。“模型”不依赖“视图”和“控制器”,也就是说,模型不关心它会被如何显示或是如何被操作。但是模型中数据的变化一般会通过一种刷新机制被公布。为了实现这种机制,那些用于监视此模型的视图必须事先在此模型上注册,从而,视图可以了解在数据模型上发生的改变。(比较:观察者模式(软件设计模式))


视图(View):视图层能够实现数据有目的的显示(理论上,这不是必需的)。在视图中一般没有程序上的逻辑。为了实现视图上的刷新功能,视图需要访问它监视的数据模型(Model),因此应该事先在被它监视的数据那里注册。


控制器(Controller):控制器起到不同层面间的组织作用,用于控制应用程序的流程。它处理事件并作出响应。“事件”包括用户的行为和数据模型上的改变

请大家记住“MVC思维“及上面这些描述,后面还会引用这些。

PureMVC框架

PureMVC框架的目标很明确,即把程序分为低耦合的三层:Model、View和Controller。降低模块间的耦合性,各模块如何结合在一起工作对于创建易扩展,易维护的应用程序是非常重要的。

在PureMVC实现的经典MVC元设计模式中,这三部分由三个单例模式类管理,分别是Model
、View和Controller。三者合称为核心层核心角色。PureMVC中还有另外一个单例模式类——Fa?adeFa?ade提供了与核心层通信的唯一接口,以简化开发复杂度

图: PureMVC设计示意图(摘自官方网站

从设计图中可以清楚看到,除了核心层、Fa?ade之外,还有Mediator、Proxy、Command


Proxy: Model 保存对 Proxy
对象的引用,Proxy
负责操作数据模型,与远程服务通信存取数据。


Mediator: View 保存对 Mediator
对象的引用。由 Mediator
对象来操作具体的视图组件,包括:添加事件监听器,发送或接收 Notification
,直接改变视图组件的状态。这样做实现了把视图和控制它的逻辑分离开来。


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
ViewController。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映射:


Proxy: Proxy 负责操作数据模型,与远程服务通信存取数据;


Mediator: Mediator操作具体的视图组件UI,包括:添加事件监听器,发送或接收
Notification ,直接改变视图组件的状态;


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命令

                   }

这个类有几点需要说明的:


它是PureMVC应用程序的入口。ApplicationFacade
类对象负责初始化Controller(控制器),建立Command与Notification
名之间的映射。


ApplicationFacade 类仅定义Notification(通知)常量:STARTUP
(private),标识应用程序启动,其它Notification(通知)常量抽离到ApplicationConstants中定义,这样更简洁、清晰。


为了使ApplicationFacade结构更清晰,简洁。将注册Command、Proxy、View&Mediator的工作抽离到BootstrapCommandsBootstrapModelsBootstrapViewMediators去做。


BootstrapCommands:初始化应用程序事件与Command之间的映射关系;


BootstrapModels:Model
初始化,初始化应用程序启动过程中需要用到的Proxy,并注册;


BootstrapViewMediators:View
初始化,唯一创建并注册ApplicationMediator,它包含其他所有View Component并在启动时创建它们。

调用startUp()启动应用程序,发送STARTUP命令;然后触发StartupCommand,它包含三个子command执行(这里借鉴Robotlegs的思想,将Command、Model、ViewMediator初始化工作分离,使得程序结构更清晰。)

图2: StartupCommand包含3个子命令BootstrapCommandsBootstrapModelsBootstrapViewMediators

简而言之,框架初始化流程可以表示如下:

图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个关卡供玩家选择;关卡具体模块,呈现特定关卡的详细信息,如需要消除的棋子、总时间、重排棋子按钮、获取提示按钮、暂停按钮等等。

关卡数据的几点说明:


关卡数据可以通过关卡编辑器生成配置文件,然后读取配置文件即可。这样关卡更可控,且可以摆放成各种形状(心形、回形等等)的数据。


如果做成网络版的,关卡数据也可以通过服务器端返回。

本实例为了简单,关卡数据根据等级随机生成10种图片,数量固定为40个。

关卡难度递进设定:


第一,从时间递减来增加难度,30 * (11 - level)。


第二,从可以重新摆放棋子、获取提示次数来体现。


如果作为一个商用连连看,还可以通过棋子的种类数量、定时随机重排增加难度。

判断连通算法,可以使用多种:分支界定判断广度优先搜索法四个方向的A*算法等等,例子不考虑性能简单的使用了分支界定判断,而且这里也不会深入介绍,毕竟我的目的不是介绍连连看算法,有兴趣的自行了解。

实现

详细代码我已放到GitHub:https://github.com/saylorzhu/linkupGame,自行前往查看。我们做技术的,看代码就可以了解如何使用PureMVC了,看到这里更推荐你去看代码。下面我只介绍代码的几个PureMVC实现的关键地方。

注意点1:PureMVC框架初始化流程,及代码组织

这也是我想推送给大家的,框架初始化流程可参见【2.
推荐PureMVC初始化流程】,代码组织方法可参见【3.
推荐PureMVC结构】。

注意点2:模块划分与通信

游戏分为2个模块:关卡选择模块、关卡具体模块。


关卡选择模块包括:

framework.view.componets.ChooseLevCanvasMediator

framework.view.componets.ui.ChooseLevCanvas

framework.controller.commands.ChooseLevCommand

该模块不用存储数据,没有Proxy。

ChooseLevCanvas、ChooseLevCanvasMediator组成View,ChooseLevCanvas负责具体UI展示和操作响应,ChooseLevCanvasMediator负责将来自用户操作的事件转发给PureMVC框架并将来自框架的事件转发给ChooseLevCanvas。

ChooseLevCommand由玩家选择具体关卡事件ApplicationConstants.SELECT_LEVEL触发,该Command调用LevelProxy的接口生成关卡数据,然后通知到“关卡具体模块”开始游戏。


关卡具体模块包括:

framework.view.componets.LevelCanvasMediator

framework.view.componets.ui.LevelCanvas

framework.model.LevelProxy

framework.controller.commands.ReSortCommand

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。


消息ID为字符串,虽然字符串可以做到编译时解耦,但无法做到消息强类型,这样错误将推迟到运行时才能发现。


消息命名,在一个大型项目中,需要一套详细的规则。相信我,否则你会吃苦头的。特别是多人参与项目中,如果没有按照一定规则命名,命名冲突可是会让你调试一阵。但不管你如何定义命名规则,【记住】为了模块间解耦,Notification发布者应该不关心谁对这个消息感兴趣(谁来处理),感兴趣者自行注册(Mediator通过listNotificationInterests注册、Command通过facade.registerCommand()注册)。例如当Proxy中用户信息改变时,不应该sendNotification通过“UpdateUserInfoVIew”、“UpdateFriendListView”2个通过来分别更新用户信息、好友列表中对应用户的信息,而只是发送一个通知,如“UpdateUserInfo”,用户信息栏、好友列表都注册这个消息,然后分别处理。


无法知道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框架使用多重设计模式来实现解耦彻底、灵活性。


单例(singleton)模式保证一个类仅有一个实例,并提供一个访问它的全局访问点。在PureMVC实现的经典MVC元设计模式中,这三部分由三个单例类管理,分别是Model
、View和Controller。PureMVC中还有另外一个单例类——Fa?ade,为子系统提供统一接口;


在PureMVC中模块间使用观察者(Observer)模式通信,以便当一个对象的状态发生改变时,所有依赖它的对象都得到通知并更新。如Proxy中数据更新了,sendNotification通知所有相关显示的地方更新显示;


使用外观(Fa?ade)模式为子系统Model、View、Controller接口提供一致对外的界面,定义了一组高层接口,这使得子系统更容易使用;


使用中介者(Mediator)模式来封装UI与系统中其他对象的交互,使得各对象不需要显示地互相引用,从而使得其耦合松散,而且可以独立地改变它们之间的交互;


使用代理(Proxy)模式为数据对象提供代理以控制数据对象的访问,PureMVC中Proxy负责操作数据模型,与远程服务信存取数据;


使用命令(Command)模式将请求封装为一个对象,实现“行为请求者”与“行为实现者”解耦将发出命令的责任和执行命令的责任分割开。

下面详细介绍PureMVC框架中使用的模式。

1. 
单例模式

单例(singleton)模式,保证一个类仅有一个实例,并提供一个访问它的全局访问点。它有如下特点:

1)       
类只能有一个实例;

2)       
它必须自行创建这个实例;

3)       
它必须自行向整个系统提供这个实例。

图:单例模式类图

一般语言可以将构造函数置为private,以阻止外部实例化。然而由于AS3中的构造函数必须是public,所以不可以像其它编程语言一样,将构造函数置为private来阻止调用构造函数生成实例。PureMVC中这样实现单例类,例如View单例:


View


public class View implements IView

{

/**

* @throws Error Error if Singleton instance has already been constructed

*/

public function View( )

{

if (instance != null) throw Error(SINGLETON_MSG);

instance = this;

mediatorMap = new Array();

observerMap = new Array();

}


public static function getInstance() : IView

{

if ( instance == null ) instance = new View( );

return instance;

}

}

另一种通过保外类实现单例的方式,可以参考这篇文章【游戏中的背景音乐和声效】http://goo.gl/PGPLL

PureMVC中有4个类都使用了单例模式:Fa?ade(外观模式常使用单例模式)、Model、View、Controller。

2. 
外观模式

外观(Fa?ade)模式为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用

图:fa?ade模式效果

引入外观角色之后,用户只需要直接与外观角色交互,用户与子系统之间的复杂关系由外观角色来实现,从而降低了系统的耦合度。外观模式特点如下:


外观模式为复杂子系统提供了一个简单接口,并不为子系统添加新的功能和行为【注意】


外观模式实现了子系统与客户之间的松耦合关系。


外观模式没有封装子系统的类,只是提供了简单的接口。如果应用需要,它并不限制客户使用子系统类。因此可以在系统易用性与通用性之间选择。


外观模式注重的是简化接口,它更多的时候是从架构的层次去看整个系统,而并非单个类的层次。


外观模式经常使用单例实现,但子系统们可以有多个Fa?ade。

在PureMVC中,为Model、View、Controller类提供了外观类Fa?ade,Fa?ade统一对外提供这3者的接口,使用过程成只需要跟Fa?ade打交道就行。

图:fa?ade与Model、View、Controller类

3. 
观察者模式

观察者(Observer)模式(有时又被称为发布/订阅模式),定义对象间的一种一对多依赖关系,使得一个对象状态发送改变时,其相关依赖对象皆得到通知并被自动更新。这里涉及到3个角色:


观察者(订阅者):被通知的对象,它需要事先注册对应消息/主题。


通知者(发布者):发生改变的对象,当状态发生改变时通知所有依赖它的观察者更新。


消息(主题):消息/主题标识观察者感兴趣的内容、通知者状态改变时需要发布的内容。

为了彻底解耦,避免直接的函数调用,PureMVC使用观察者模式(发布/订阅)的形式传递消息。在PureMVC中Mediator、Proxy、Command之间的通信,以通知形式实现松散耦合。Mediator、Proxy、Command都是通知者(发布者)可以调用sendNotification发送消息Mediator、Command同时也是观察者(订阅者)可以接收来自其它对象的通知

观察者模式中,维护观察者、通知者、消息/主题之间的映射关系有多种方式:

1)       
由通知者维护对应的消息/主题、观察者的映射关系,并在自身状态发送改变时,通知所有的观察者;

2)       
由一个管理器维护所有消息/主题、观察者之间的映射关系,当通知者发布消息时,通过管理器调用观察者通知更新。(PureMVC使用这种模式,View是管理器,管理全局的消息映射关系

图:通知者、观察者、管理器、消息类

3.1.         
发布通知

所有的通知者(发布者)都继承自Notifier类,故拥有发布通知的功能,然而Notifier是使用Fa?ade发布通知的。


Notifier


public class Notifier implements INotifier

{

public function
sendNotification( notificationName:String, body:Object=null, type:String=null ):void

{

facade.sendNotification( notificationName, body, type );

}

// Local reference to the Facade Singleton

protected var facade:IFacade = Facade.getInstance();

}

由前面介绍的外观模式可知fa?ade保存了View的引用,View在观察者模式中充当着管理者的角色,保存着所有消息/主题、观察者的映射。当Mediator/Command/Proxy发布通知时,fa?ade调用View的notifyObservers()方法,遍历保存的映射关系,从而通知所有满足条件的观察者。PureMVC中Mediator/Command/Proxy发布通知的时序如下图所示:

图:发布通知时序

3.2.         
注册通知

观察者想接受到相应通知必须先注册,Mediator、Command分别通过以下接口注册:


listNotificationInterests():Array


registerCommand( notificationName : String, commandClassRef : Class ) : void

Mediator、Command从注册到接收通知的时序图如下:

图:注册、接收通知时序

下篇将介绍PureMVC中的中介者模式、代理模式、命令模式。

PureMVC(AS3)剖析:设计模式(二)

2013-03-25 14:00 by 吴秦,
2893 阅读, 7 评论, 收藏
编辑

PureMVC(AS3)剖析:设计模式(二)


模式

上一篇中介绍了PureMVC中使用的3种设计模式:单例模式、观察者模式、外观模式。本篇将继续介绍剩下的3种设计模式:


使用中介者(Mediator)模式来封装UI与系统中其他对象的交互,使得各对象不需要显示地互相引用,从而使得其耦合松散,而且可以独立地改变它们之间的交互;


使用代理(Proxy)模式为数据对象提供代理以控制数据对象的访问,PureMVC中Proxy负责操作数据模型,与远程服务信存取数据;


使用命令(Command)模式将请求封装为一个对象,实现“行为请求者”与“行为实现者”解耦将发出命令的责任和执行命令的责任分割开。

1. 
中介者模式

中介者(Mediator)模式:用一个中介对象来封装一系列对象交互。中介者使各对象不需要相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。适用性:(摘自:《设计模式:可复用面向对象软件的基础》)


一组对象以定义良好但是复杂的方式进行通信。产生的相互依赖关系结构混乱且难以理解。


一个对象引用其他很多对象并且直接与这些对象通信,导致难以复用该对象。


想定制一个分布在多个类中的行为,而又不想生成太多的子类。

在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)模式:为其它的对象提供一种代理,以控制对这个对象的访问。按照使用目的来划分,代理有以下几种:(摘自:《设计模式:可复用面向对象软件的基础》)


远程(Remote)代理:为一个位于不同的地址空间的对象提供一个局域代表对象。这个不同的地址空间可以是在本机器中,也可是在另一台机器中。远程代理又叫做大使(Ambassador)。


虚拟(Virtual)代理:根据需要创建一个资源消耗较大的对象,使得此对象只在需要时才会被真正创建。


Copy-on-Write代理:虚拟代理的一种。把复制(克隆)拖延到只有在客户端需要时,才真正采取行动。


保护(Protect or Access)代理:控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限。


Cache代理:为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。


防火墙(Firewall)代理:保护目标,不让恶意用户接近。


同步化(Synchronization)代理:使几个用户能够同时使用一个对象而没有冲突。


智能引用(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


降低对象之间的耦合度。


新的命令可以很容易地加入到系统中。


可以比较容易地设计一个组合命令。


调用同一方法实现不同的功能

命令模式缺点:

使用命令模式可能会导致某些系统有过多的具体命令类。因为针对每一个命令都需要设计一个具体命令类,因此系统可能需要大量具体命令类,这将影响命令模式的使用。

时间: 2024-11-08 23:07:42

MVC 编程笔记2的相关文章

MVC 编程笔记1

转载自https://www.ibm.com/developerworks/cn/java/j-lo-puremvc/index.html?ca=drs- 引言 MVC 是一个非常常见的设计模式,被广泛运用于各种和用户交互的软件中,像 java Swing 类库和 SWT.J2EE 中的 JSP.wxWidgets.QT 和 Gnome 等.可以很不夸张的说,几乎所有和 UI 有关的类库,不论什么语言,什么平台都或多或少有 MVC 的影子.但是经过这么多年的发展,MVC 这个设计模式已经由最初

C#编程笔记一

---恢复内容开始--- 编程笔记 override与new实现的版本控制 // versioning.cs // CS0114 expected public class MyBase { public virtual string Meth1() { return "MyBase-Meth1"; } public virtual string Meth2() { return "MyBase-Meth2"; } public virtual string Met

Spring MVC 学习笔记(二):@RequestMapping用法详解

一.@RequestMapping 简介 在Spring MVC 中使用 @RequestMapping 来映射请求,也就是通过它来指定控制器可以处理哪些URL请求,相当于Servlet中在web.xml中配置 <servlet>     <servlet-name>servletName</servlet-name>     <servlet-class>ServletClass</servlet-class> </servlet>

MVC学习笔记索引帖

[MVC学习笔记]1.项目结构搭建及单个类在各个层次中的实现 [MVC学习笔记]2.使用T4模板生成其他类的具体实现 [MVC学习笔记]3.使用Spring.Net应用IOC(依赖倒置) [MVC学习笔记]4.使用Log4Net来进行错误日志的记录 [MVC学习笔记]5.使用Controller来代替Filter完成登录验证(Session校验) [MVC学习笔记]6. 使用Memcache+Cookie解决分布式系统共享登录状态 [MVC学习笔记]7.使用极验验证来制作更高逼格的验证码

python核心编程--笔记

python核心编程--笔记 的解释器options: 1.1 –d   提供调试输出 1.2 –O   生成优化的字节码(生成.pyo文件) 1.3 –S   不导入site模块以在启动时查找python路径 1.4 –v   冗余输出(导入语句详细追踪) 1.5 –m mod 将一个模块以脚本形式运行 1.6 –Q opt 除法选项(参阅文档) 1.7 –c cmd 运行以命令行字符串心事提交的python脚本 1.8 file   以给定的文件运行python脚本 2 _在解释器中表示最后

C++windows内核编程笔记day07_day08,可视化建菜单、加速键使用、绘图等

可视化操作创建的菜单,加载到窗口. 方法1:注册时指定菜单 wce.lpszMenuName=MAKEINTRESOURCE(IDR_MENUMAIN);//数字形式的资源ID转换为字符串形式的资源 方法2: //创建窗口时加载菜单资源 HMENU menumain= LoadMenu(g_hinstance,MAKEINTRESOURCE(IDR_MENUMAIN)); menumain 传入 CreateWindowEx();//倒数第三个参数 窗口指定小图标: 1.注册时指定 wce.hI

C++windows内核编程笔记day09_day10,对话框和窗口基本控件等的使用

//设置字体颜色 SetTextColor(hdc,RGB(255,0,0)); //窗口背景 //wce.hbrBackground=(HBRUSH)(COLOR_WINDOW+1); //wce.hbrBackground=CreateSolidBrush(RGB(0,0,255)); //设置字体背景 SetBkColor(hdc,RGB(0,0,200)); //设置字体背景模式 SetBkMode(hdc,TRANSPARENT);//字体背景透明 //创建字体,成功返回字体,失败返回

MVC 编程模型及其变种

MVC 编程模型及其变种 MVC全称是Model View Controller, 这是一个模型(model)-查看(view)-调节器(controller)缩写,这是通过通用的编程模型非.MVC当为了分离数据和视图首次提出,数据和实体之间的关系.这就是关注点分离的设计理念,也是单一职责原则(Single-Resposibility Principle). 在我们的开发过程中, 视图用来展现界面, 我们的模型处理业务, 持有数据, 而控制器是他们之间的中间人. GoF四人组觉得MVC是"一组用

理解ASP.NET中MVC 编程模型 上

MVC 编程模型 MVC 是 ASP.NET 开发模型之一. MVC 是用于构建 web 应用程序的一种框架,使用 MVC (Model View Controller) 设计: Model(模型)表示应用程序核心(比如数据库记录列表) View(视图)对数据(数据库记录)进行显示 Controller(控制器)处理输入(写入数据库记录) MVC 模型同时提供对 HTML.CSS 以及 JavaScript 的完整控制. MVC 模型通过三个逻辑层来定义 web 应用程序: business l