PureMVC--一款多平台MVC框架
下载:https://github.com/PureMVC/puremvc-csharp-multicore-framework/tree/1.1.0
API文档:http://puremvc.org/pages/docs/CSharp/multicore/
引子
了解设计模式的人应该都多少听说过MVC模式。
严格意义上来说,“MVC模式”是一个伪概念,因为MVC并不属于设计模式,至少不属于GoF的23种设计模式之一,而更像是一个设计模式的结合体:V和C之间会实现观察者模式,M内部会实现单例模式,C在派发任务时会实现Command模式。
不得不说,MVC模式对软件的高可扩展性和高可维护性做出了巨大的贡献,这也使得MVC模式成为很多中等规模甚至大规模软件的常用框架,且经历了20余年仍旧在软件开发领域流行并通用,足可见MVC模式的经典。
但是传统MVC模式真的那么完美吗?
传统MVC的痛点
让我们一个个来说。
Controller:控制器,包含了项目的业务逻辑。但是也是被大家吐槽最多的一个,原因就是很多人,或者说大多数人,习惯于什么都往Controller里写,最后一个Controller超过1000行代码是司空见惯的事。所以关于传统MVC的第一个痛点就是,Controller过于臃肿。
Model:模型,包含了项目的数据模型。MVC定义之初,Model是核心,旨在使得同一个Model可以被复用到多个项目或者被复用到同一个项目的不同模块之中。但是在实际项目中,Model还承载着纯Model层内部的运算的工作,但是运算部分会项目的不同而有所区别,因此与项目的适配反而成为了Model可复用的枷锁。所以关于传统MVC的第二个痛点就是,Model变得不可复用。
View:视图,包含了项目所有的UI组件。视图本身没有什么好被大家诟病的,但是由于MVC中对于View和Controller界限的模糊界定造成了使用者在写代码的时候会觉得这部分代码放在View或者Controller里都可以的情况。例如事件的处理,组件的组合等。所以关于传统MVC的第三个痛点就是,View概念的模糊。
PureMVC
既然上文说的是传统MVC,那么可以判定PureMVC是一个新型MVC。
其实PureMVC只是相对于传统MVC(20年陈酿)来说“新”一些而已,因为PureMVC今年也已经有10年的历史了。
PureMVC是一款基于MVC的开源框架,最初是为基于ActionScript3的Flash,RIA程序开发的,后来被移植到16种语言平台上。
PureMVC分为标准版本和多核版本,后者为程序的模块化开发提供了支持。本文以标准版为例分析PureMVC。
PureMVC的MVC
在PureMVC实现的MVC模式中,MVC分别由三个单例模式来管理,三者成为PureMVC的核心层。
Model与Proxy
Proxy(模式),提供了一个一个包装器或一个中介被客户端调用,从而达到去访问在场景背后的真实对象。Proxy模式可以方便的将操作转给真实对象,或者提供额外的逻辑。
在PureMVC中,Model保存了对Proxy对象的引用,Proxy去操作具体的数据模型(Data Object)。也就是说,Proxy管理Data Object以及对Data Object的访问。
View与Mediator
Mediator(模式),定义了一种封装对象之间交互的中介。这种设计模式被认为是行为模式因为它可以改变模式的运行行为。
正如定义里所说,PureMVC中,View只关心UI,具体的对对象的操作由Mediator来管理,包括添加事件监听,发送或接受Notification,改变组件状态等。这也解决了视图与视图控制逻辑的分离。
Controller与Command
Command(模式),是一种行为设计模式,这种模式下所有动作或者行为所需信息被封装到一个对象之内。Command模式解耦了发送者与接收者之间的联系。
在PureMVC中,Controller保存了所有Command的映射。Command是无状态且惰性的,只有在需要的时候才被创建。
Facade
与传统MVC模式不用的是,PureMVC中对于Model,View,Controller的调用是基于Facade模式的。
Facade模式,对应了GoF中的Facade模式,是一种将复杂且庞大的内部实现暴露为一个简单接口的设计模式,例如对大型类库的封装。
在PureMVC中,Facade是与核心层(Model,View,Controller)进行通信的唯一接口,目的是简化开发复杂度。实际编码过程中,不需要手动实现这三类文件,Facade类在构造方法中已经包含了对这三类单例的构造。
PureMVC各层之间的交互
View层的Mediator可以和Model层的Proxy进行互相访问,但是PureMVC设计之初是希望只有View依赖于Model,反之不成立。也就是View可以知道Model层有什么,但是Model层不需要知道View的任何内容。Mediator访问数据可以直接通过Proxy来完成,但是如果要对Proxy具体的内容进行加工,必须要通过Controller的Command来完成,这有助于实现View和Model之间的松散耦合。
如上文所说,Proxy最好不要直接调用Mediator来通知它请求完成,而是在异步取到数据之后,通过Notification来进行通知。Proxy只发送通知,不应该监听通知,因为Proxy属于Model层,不应该知道View层的状态变化。当然,Proxy应当对外提供数据变更的接口。
Command的实例化与执行只能由Controller来做。作为控制逻辑的执行体,Command有权拿到Proxy和Mediator的对象,并进行值加工,最后会将结果通过Notification发送给其它Command或者Mediator。
业务逻辑 VS 域逻辑
你可能会遇到这个问题:某段逻辑到底是应当放在Proxy(Model)里,还是应该放在Command(Controller)里?
其实这个问题可以引申为业务逻辑与域逻辑的区别。
- 业务逻辑
指的是那些需要协调Model与View的逻辑。 - 域逻辑
指的是仅仅是针对数据模型的操作,不论是对于客户端还是对于服务端,不论是同步的操作还是异步的操作。
因此,业务逻辑理所当然应该放在Command里来完成,而域逻辑应当放在Proxy里完成。
案例分析
这里以笔者实现的一个简单的计算程序为例来分解PureMVC。
创建Facade
这里的关键点是实现startup方法和initializeController,示例如下:
ApplicationFacade.m
- (void)startup:(id)app
{
[self sendNotification:StartUp body:app];
}
具体的初始化方法放到了StartUpCommand中,包括创建视图,注册Proxy以及注册Mediator:
StartUpCommand.m
- (void)execute:(id<INotification>)notification
{
UIWindow *appWindow = [notification body];
ViewController *viewController = [[ViewController alloc] init];
appWindow.rootViewController = viewController;
appWindow.backgroundColor = [UIColor whiteColor];
[appWindow makeKeyAndVisible];
// register mediators
[facade registerMediator:[ViewMediator withViewComponent:viewController]];
// register proxys
[facade registerProxy:[ElementProxy proxy]];
}
创建ViewComponent和对应Mediator
本例中只有一个View,负责UI显示。当用户点击“=”时出发操作,此时内部将此事件抛到对应代理中,对应代码如下:
ViewController.h
@protocol ViewControllerDelegate <NSObject>
- (void)addNumberA:(CGFloat)numberA andNumberB:(CGFloat)numberB;
@end
ViewController.m
- (void)addTwoNumbers
{
if (self.delegate && [self.delegate respondsToSelector:@selector(addNumberA:andNumberB:)]) {
[self.delegate addNumberA:[self.inputA.text floatValue] andNumberB:[self.inputB.text floatValue]];
}
}
在对应Mediator中要关注四个方法:
- onRegister,负责给对应的ViewComponent添加事件或代理:
- (void)onRegister
{
[self.viewComponent setDelegate:self];
}
- listNotificationInterests,像Facade注册Mediator关心的Notification列表。当向Facade发送Notification时会遍历每一个Mediator的InterestList,会根据这个列表进行事件响应。
- handleNotification,一旦向Facade发送的事件命中listNotificationInterests列表则会回调到这个函数,此处应放接收事件后的逻辑。
- 实现对应ViewComponent的事件或者代理方法。本例中为
- (void)addNumberA:(CGFloat)numberA andNumberB:(CGFloat)numberB
方法。
创建DataObject和对应Proxy
本例中,DataObject只保存业务相关的变量,numberA,numberB,result。
本例中业务逻辑由于很简单,因此Proxy只封装了对DataObject中变量的存取以及变量是否可以操作的判断。
ElementProxy.h
@interface ElementProxy : Proxy
- (void)setNumberA:(NSNumber *)numberA andNumberB:(NSNumber *)numberB;
- (NSNumber *)getNumberA;
- (NSNumber *)getNumberB;
- (void)setResult:(NSNumber *)result;
- (NSNumber *)getResult;
- (BOOL)canOperate;
@end
创建Controller和Command
在PureMVC中,Controller已经在Facade的实例化中被隐式创建好,因此只需要创建对应的Command并且在Facade中进行注册即可。
- (void)initializeController
{
[super initializeController];
[self registerCommand:StartUp commandClassRef:[StartUpCommand class]];
[self registerCommand:AddTwoNumbers commandClassRef:[AddTwoNumbersCommand class]];
}
对应Command的逻辑:
- (void)execute:(id<INotification>)notification
{
ElementProxy *elmentProxy = (ElementProxy *)[facade retrieveProxy:[ElementProxy NAME]];
NSNumber *numberA = elmentProxy.getNumberA;
NSNumber *numberB = elmentProxy.getNumberB;
NSNumber *result = [NSNumber numberWithFloat:([numberA floatValue]+ [numberB floatValue])];
[elmentProxy setResult:result];
[facade sendNotification:ShowResult];
}
模块间交互顺序图
如图所示,在接收到外部事件后,viewCompoent第一时间将事件抛到ViewMediator中,后者将事件相关变量存到Proxy进而存到了VO,也就是DataObject里。之后ViewMediator发送需要操作的命令通知addNumberNotification,Facade将此通知分配给实现注册好的addNumberCommand。Command从Proxy拿到相关变量后,运算,并将结果写到Proxy中,最后向Facade发送可以显示结果的通知showResultNotification。Facade将此通知转发给之前加过此通知到interest list的ViewMediator,Mediator从Proxy处取结果后把结果通过ViewComponent暴露出来的接口设置好,至此一次完整PureMVC交互流程完成。
猛回头
回到文章的开头,PureMVC到底如何解决了传统MVC的三个痛点?
Controller将操作逻辑细化为Command
根据PureMVC的最佳实践,Controller实体不需要单独实现,且Controller内部将每一个操作分割为一个个Command,这从根本上解决了Controller越来越臃肿的问题,强制用户将Controller里每一个操作细粒度化,使得代码可读性更强,维护性更高。
Proxy负责域逻辑,DataObject负责数据模型
PureMVC中,与域相关的逻辑和接口由Proxy来负责,后续的添加和修改接口只在Proxy中完成。而DataObject是完全对业务进行数据建模而产生的数据模型,与业务没有丝毫的关系,因此也保证了高可移植性。
ViewComponent只关注UI,其余的交给Mediator
PureMVC规定了ViewComponent只负责UI的绘制,而其他事情,包括事件的绑定统统交给Mediator来做。这也就避免了ViewComponent内部代码定义模糊,更不会和Controller的代码进行混淆。
后记
记得第一次接触PureMVC是在2009年左右,当时刚接触编程没多久的我读着师兄的解读一遍一遍的用actionScript进行实现,虽然没完全懂为什么有那些模块,模块之间为什么要那样通信,但是开始体会到框架的魅力和使用的乐趣。
随着工作年限的增加和编程经验的增长,越来越觉得这款框架固化了我很多正确的观念,这些观念渐渐的让我对之后的编程有了正确的感觉,所以PureMVC可以称得上是我框架方面的启蒙老师。
但是很遗憾的是,随着Adobe Flash平台的没落,这款在ActionScript上广为流行的框架也变的风光不再,即便它已经被翻译成16种程序语言。
所以我决定在时隔这么久重新学习这个框架,将框架运用到简单的例子中,解决在GitHub上没有可运行的iOS版本PureMVC Demo的尴尬情景。(官方Demo还停留在iOS3.0上)
希望教师节这天,我能帮我这位老师弹弹尘土,让更多的人重新关注到它。毕竟,好的框架值得任何一门语言来借鉴。
本文涉及代码地址
本文关联文章
Reference
PureMVC Best Practise
Facade Pattern: WikiPedia
Mediator Pattern:WikiPedia
Proxy Pattern:Wikipedia
Command Pattern:WikiPedia
原文地址:http://www.jianshu.com/p/47deaced9eb3
PureMVC--一款多平台MVC框架的更多相关文章
- 深入详解美团点评CAT跨语言服务监控(九)CAT管理平台MVC框架
在第2章我们讲到,服务器在初始化CatServlet 之后, 会初始化 MVC,MVC也是继承自AbstractContainerServlet , 同样也是一个 Servlet 容器,这是一个非常古 ...
- 全端开发必备!10个最好的 Node.js MVC 框架
Node.js 是最流行的 JavaScript 服务端平台,它允许建立可扩展的 Web 应用程序.Node.js 包含不同类型的框架,如 MVC 框架.全栈框架.REST API 以及大量的服 ...
- 【360开源】thinkjs:基于Promise的Node.js MVC框架 (转)
thinkjs是360奇舞团开源的一款Node.js MVC框架,该框架底层基于Promise来实现,很好的解决了Node.js里异步回调的问题.360奇舞团(奇虎75Team),是奇虎360公司We ...
- MVC框架请求处理
为开发团队选择一款优秀的MVC框架是件难事儿,在众多可行的方案中决择需要很高的经验和水平.你的一个决定会影响团队未来的几年.要考虑方面太多: 简单易用,以提高开发效率.使小部分的精力在框架上,大部分的 ...
- Ninject是一款.Net平台下的开源依赖注入框架
Ninject是一款.Net平台下的开源依赖注入框架.按照官方说法,它快如闪电.超级轻量,且充分利用了.Net的最新语法,使用Lambda表达式代替Xml文件完成类型绑定.Ninject结构精巧,功能 ...
- 【转】12 款优秀的 JavaScript MVC 框架评估
JavaScript MVC 框架有很多,不同框架适合于不同项目需求.了解各种框架的性能及优劣有利于我们更加快捷的开发.作者(Gordon L.Hempton)一直在寻求哪种MVC框架最为完美,他将目 ...
- 十款最佳Node.js MVC框架
十款最佳Node.js MVC框架摘要:Node.js是JavaScript中最为流行的框架之一,易于创建可扩展的Web应用.本文分享十款最佳的JavaScript框架. Node.js是JavaSc ...
- 私人定制,十款最佳Node.js MVC框架
Node.js是JavaScript中最为流行的框架之一,易于创建可扩展的Web应用.本文分享十款最佳的JavaScript框架. Node.js是JavaScript中最为流行的框架之一,易于创建可 ...
- 框架学习笔记:Unity3D的MVC框架——StrangeIoC
作为从AS3页游走过来的人,看见StrangeIoC会额外亲切,因为StrangeIoC的设计和RobotLegs几乎一致,作为一款依赖注入/控制反转(IoC)的MVC框架,StrangeIoC除了使 ...
随机推荐
- linux下使用shell脚本自动化部署项目
在Java开发项目时经常要把正在开发的项目发布到测试服务器中去测试,一般的话是要把项目先打成war包,然后把war包发布到服务器中,关闭服务器, 最后重新启动服务器,虽然这过程不是很繁琐,但如果是多个 ...
- 利用ItextSharp 生成PDF文档改进版
导入的ItextSharp.dll一定要是较高的版本 数据库表结构 生成的PDF样式 代码: namespace WebPDF { public partial class _Default : Sy ...
- bazel-demo2_1
demo2_1目录树 ├── app │ ├── BUILD │ ├── hello_world.cpp │ └── lib │ ├── BUILD │ ├── func.cpp │ └── func ...
- python traceback捕获并打印异常
异常处理是日常操作了,但是有时候不能只能打印我们处理的结果,还需要将我们的异常打印出来,这样更直观的显示错误 下面来介绍traceback模块来进行处理 try: 1/0 except Excepti ...
- centos修改ip mac等
CentOS修改mac http://www.haowlan.com/jishuluntan/488.html CentOS 修改IP地址, DNS, 网关 http://www.21andy.com ...
- C语言 · 高精度乘法
算法提高 高精度乘法 时间限制:1.0s 内存限制:256.0MB 问题描述 在C/C++语言中,整型所能表示的范围一般为-231到231(大约21亿),即使long long型,一 ...
- mysql 函数模拟序列
mysql本身不提供序列机制,但是可以通过函数来模拟实现序列 CREATE TABLE IF NOT EXISTS `sequence` ( `id` ) CHARACTER SET utf8 COL ...
- cglib 动态代理基础篇
cglib 动态代理基础篇 CGlib是什么? CGlib是一个强大的,高性能,高质量的Code生成类库.它可以在运行期扩展Java类与实现Java接口. 下面我们将通过一个具体的事例来看一下CGli ...
- Ext.core.DomQuery Dom选择器
Ext.dom.Query Element Selectors:(元素选择器) Ext.core.DomQuery.select('表达式') 返回HTMLElement[] * any elem ...
- Java NIO使用及原理分析(二)(转)
在第一篇中,我们介绍了NIO中的两个核心对象:缓冲区和通道,在谈到缓冲区时,我们说缓冲区对象本质上是一个数组,但它其实是一个特殊的数组,缓冲区对象内置了一些机制,能够跟踪和记录缓冲区的状态变化情况,如 ...