PureMVC--一款多平台MVC框架

廉颇老矣,尚能饭否? --辛弃疾git

引子

了解设计模式的人应该都多少据说过MVC模式。 严格意义上来讲,“MVC模式”是一个伪概念,由于MVC并不属于设计模式,至少不属于GoF的23种设计模式之一,而更像是一个设计模式的结合体:V和C之间会实现观察者模式,M内部会实现单例模式,C在派发任务时会实现Command模式。 不得不说,MVC模式对软件的高可扩展性和高可维护性作出了巨大的贡献,这也使得MVC模式成为不少中等规模甚至大规模软件的经常使用框架,且经历了20余年仍旧在软件开发领域流行并通用,足可见MVC模式的经典。 可是传统MVC模式真的那么完美吗?github

传统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概念的模糊。bash

PureMVC

既然上文说的是传统MVC,那么能够断定PureMVC是一个新型MVC。架构

其实PureMVC只是相对于传统MVC(20年陈酿)来讲“新”一些而已,由于PureMVC今年也已经有10年的历史了。mvc

PureMVC是一款基于MVC的开源框架,最初是为基于ActionScript3的Flash,RIA程序开发的,后来被移植到16种语言平台上app

PureMVC分为标准版本和多核版本,后者为程序的模块化开发提供了支持。本文以标准版为例分析PureMVC。框架

PureMVC的MVC

PureMVC架构图
在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。

PureMVC Demo

建立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];
}
复制代码

模块间交互顺序图

Sequence Diagram
如图所示,在接收到外部事件后,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上) 但愿教师节这天,我能帮我这位老师弹弹尘土,让更多的人从新关注到它。毕竟,好的框架值得任何一门语言来借鉴。

本文涉及代码地址

nimoment/PureMVC_practise

##Reference PureMVC Best Practise Facade Pattern: WikiPedia Mediator Pattern:WikiPedia Proxy Pattern:Wikipedia Command Pattern:WikiPedia

相关文章
相关标签/搜索