廉颇老矣,尚能饭否? --辛弃疾git
了解设计模式的人应该都多少据说过MVC模式。 严格意义上来讲,“MVC模式”是一个伪概念,由于MVC并不属于设计模式,至少不属于GoF的23种设计模式之一,而更像是一个设计模式的结合体:V和C之间会实现观察者模式,M内部会实现单例模式,C在派发任务时会实现Command模式。 不得不说,MVC模式对软件的高可扩展性和高可维护性作出了巨大的贡献,这也使得MVC模式成为不少中等规模甚至大规模软件的经常使用框架,且经历了20余年仍旧在软件开发领域流行并通用,足可见MVC模式的经典。 可是传统MVC模式真的那么完美吗?github
让咱们一个个来讲。 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
既然上文说的是传统MVC,那么能够断定PureMVC是一个新型MVC。架构
其实PureMVC只是相对于传统MVC(20年陈酿)来讲“新”一些而已,由于PureMVC今年也已经有10年的历史了。mvc
PureMVC是一款基于MVC的开源框架,最初是为基于ActionScript3的Flash,RIA程序开发的,后来被移植到16种语言平台上。app
PureMVC分为标准版本和多核版本,后者为程序的模块化开发提供了支持。本文以标准版为例分析PureMVC。框架
Proxy(模式),提供了一个一个包装器或一个中介被客户端调用,从而达到去访问在场景背后的真实对象。Proxy模式能够方便的将操做转给真实对象,或者提供额外的逻辑。异步
在PureMVC中,Model保存了对Proxy对象的引用,Proxy去操做具体的数据模型(Data Object)。也就是说,Proxy管理Data Object以及对Data Object的访问。
Mediator(模式),定义了一种封装对象之间交互的中介。这种设计模式被认为是行为模式由于它能够改变模式的运行行为。
正如定义里所说,PureMVC中,View只关心UI,具体的对对象的操做由Mediator来管理,包括添加事件监听,发送或接受Notification,改变组件状态等。这也解决了视图与视图控制逻辑的分离。
Command(模式),是一种行为设计模式,这种模式下全部动做或者行为所需信息被封装到一个对象以内。Command模式解耦了发送者与接收者之间的联系。 在PureMVC中,Controller保存了全部Command的映射。Command是无状态且惰性的,只有在须要的时候才被建立。
与传统MVC模式不用的是,PureMVC中对于Model,View,Controller的调用是基于Facade模式的。 Facade模式,对应了GoF中的Facade模式,是一种将复杂且庞大的内部实现暴露为一个简单接口的设计模式,例如对大型类库的封装。
在PureMVC中,Facade是与核心层(Model,View,Controller)进行通讯的惟一接口,目的是简化开发复杂度。实际编码过程当中,不须要手动实现这三类文件,Facade类在构造方法中已经包含了对这三类单例的构造。
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。
你可能会遇到这个问题:某段逻辑究竟是应当放在Proxy(Model)里,仍是应该放在Command(Controller)里? 其实这个问题能够引伸为业务逻辑与域逻辑的区别。
所以,业务逻辑理所固然应该放在Command里来完成,而域逻辑应当放在Proxy里完成。
这里以笔者实现的一个简单的计算程序为例来分解PureMVC。
这里的关键点是实现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]];
}
复制代码
本例中只有一个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中要关注四个方法:
- (void)onRegister
{
[self.viewComponent setDelegate:self];
}
复制代码
- (void)addNumberA:(CGFloat)numberA andNumberB:(CGFloat)numberB
方法。本例中,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
复制代码
在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];
}
复制代码
回到文章的开头,PureMVC到底如何解决了传统MVC的三个痛点?
根据PureMVC的最佳实践,Controller实体不须要单独实现,且Controller内部将每个操做分割为一个个Command,这从根本上解决了Controller愈来愈臃肿的问题,强制用户将Controller里每个操做细粒度化,使得代码可读性更强,维护性更高。
PureMVC中,与域相关的逻辑和接口由Proxy来负责,后续的添加和修改接口只在Proxy中完成。而DataObject是彻底对业务进行数据建模而产生的数据模型,与业务没有丝毫的关系,所以也保证了高可移植性。
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