模型-视图-控制器(MVC)模式 - 设计模式之王
![](https://i-blog.csdnimg.cn/blog_migrate/56cd2d19cfcf72bf151ac9042a8c54d7.png)
模型
-
视图
-
控制器(
MVC)
是
Cocoa
的构建块之一,毫无疑问它是使用最频繁的设计模式。它根据通用的角色去划分类,这样就使得类的
职责可以根据角色清晰的划分开来。
涉及到的三个角色如下:
Model:
模型保存应用程序的数据,定义了怎么去操作它。例如在本应用中模型就是Album
类。
View:
视图是模型的可视化表示以及用户交互的控件;基本上来说,所有的UIView
对象以及它的子类都属于视图。在本应用中AlbumView
代表了视图。
Controller:
控制器是一个协调所有工作的中介者(Mediator
)。它访问模型中的数据并在视图中展示它们,同时它们还监听事件和根据需要操作数据。你可以猜猜哪个类是控制器吗?它正是:ViewController
。
一个
MVC
模式的好的实现也就意味着每一个对象都会被划分到上面所说的组中。
我们可以很好的用下图来描述通过控制器实现的视图到模型的交互过程:
![](https://i-blog.csdnimg.cn/blog_migrate/324c25f113c6552a7c4f112d7165c503.png)
模型会把任何数据的变更通知控制器,然后控制器更新视图数据。视图对象通知控制器用户的操作,控制器要么根据需要来更新模型,要么检索任何被请求的数据。
你可能在想为什么不能仅仅使用控制器,在一个类中实现视图和模型,这样貌似更加容易
?
所有的这些都归结于代码关注点分离以及复用。在理想的状态下,视图应该和模型完全的分离。如果视图不依赖某个实际的模型,那么视图就可以被复用来展示不同模型的数据。
举个例子来说,如果将来你打算加入电影或者书籍到你的资料库中,你仍然可以使用同样的
AlbumView
去显示电影和书籍数据。更进一步来说,如果你想创建一个新的与专辑有关联的工程,你可以很简单的复用
Album
类,因为它不依赖任何视图。这就是
MVC
的强大之处。
如何使用MVC模式
首先,你需要确保在你工程中的每个类是控制器,模型和视图中的一种,不要在一个类中组合两种角色的功能。到目前为止,你创建了一个
Album
类和
AlbumView
类,这样做挺好的。
其次,为了确保你能符合这种工作方法,你应该创建三个工程组(
Project Group
)来保存你的代码,每个工程组只存放一种类型的代码。
导航到
"
文件
\
新建
\
组(
File\New\Group
)
"
(或者按下
Command + Option + N
),命名组为
Model
,重复同样的过程来创建
View
和
Controller
组。
现在拖动
Album.h
和
Album.m
去模型组,拖动
AlbumView.h
和
AlbumView.m
去视图组,最后拖动
ViewController.h
和
ViewController.m
到控制器组。
此时工程结构应该看起来和下图类似:
![](https://i-blog.csdnimg.cn/blog_migrate/4a602592e35fe492232f705b043b2a68.png)
没有了之前所有文件都散落在各处,现在你的工程已经开起来好多了。显然你也可以有其它的组和类,但是本应用的核心包含在这三个类别中(
Model,View,Controller
)。
现在所有的组件都已经安排好了,你需要从某处获取专辑数据。你将创建一个贯穿于代码的管理数据的
API-
这也就代表将有机会去讨论下一个设计模式
-
单例(单态)模式。
单例(单态)模式
单例设计模式确保对于一个给定的类只有一个实例存在,这个实例有一个全局唯一的访问点。它通常采用懒加载的方式在第一次用到实例的时候再去创建它。
注意
:苹果大量使用了此模式。例如:
[NSUserDefaults standardUserDefaults]
,
[UIApplication sharedApplication]
,
[UIScreen mainScreen]
,
[NSFileManager defaultManager]
,所有的这些方法都返回一个单例对象。
你很可能会想为什么这么关心是否一个类有多个实例?毕竟代码和内存都是廉价的,对吗?
有一些情况下,只有一个实例显得非常合理。举例来说,你不需要有多个
Logger
的实例,除非你想去写多个日志文件。或者一个全局的配置处理类:实现线程安全的方式访问共享实例是容易的,比如一个配置文件,有好多个类同时修改这个文件。
如何使用单例模式
首先来看看下面的图:
![](https://i-blog.csdnimg.cn/blog_migrate/a34cfe1a75afe2620ad8924f8864a053.png)
上面的图描述了一个有单一属性(它就是单一实例)和
sharedInstance,init
两个方法的类。
客户端第一次发送
sharedInstance
消息的时候,
instance
属性尚未被初始化,所以此时你需要创建一个新的实例,然后返回它的引用。
当你下一次调用
sharedInstance
的时候,
instance
不需要任何初始化可以立即返回。这个逻辑保证总是只有一个实例。
你接下来将用这个模式来创建一个管理所有专辑数据的类。
你将注意到工程中有一个
API
的组,在这个组里你可以放入给你应用提供服务的所有类。在此组中,用
IOS\Cocoa Touch\Objective-C class
模板创建一个新类,命名它为
LibraryAPI,
设置父类为
NSObject.
打开
LibraryAPI.h
,用如下代码替换它的内容:
@interfaceLibraryAPI : NSObject
+ (LibraryAPI*)sharedInstance;
@end
现在打开
LibraryAPI.m
,在
@implementation
那一行后面插入下面的方法:
+ (LibraryAPI*)sharedInstance
{
// 1
static LibraryAPI *_sharedInstance = nil;
// 2
static dispatch_once_t oncePredicate;
// 3
dispatch_once(&oncePredicate, ^{
_sharedInstance = [[LibraryAPI alloc] init];
});
return _sharedInstance;
}
在这个简短的方法中,有一些需要需要注意的点:
1.
声明一个静态变量去保存类的实例,确保它在类中的全局可用性。
2.
声明一个静态变量
dispatch_once_t ,
它确保初始化器代码只执行一次
3.
使用
Grand Central Dispatch(GCD)
执行初始化
LibraryAPI
变量的
block.
这
正是单例模式的关键:一旦类已经被初始化,初始化器永远不会再被调用。
下一次你调用
sharedInstance
的时候,
dispatch_once
块中的代码将不会执行(因为它已经被执行了一次),你将得到原先已经初始化好的实例。
注意
:
为了学习更多关于
GCD
方面的信息以及如何使用,请查看本站指南
Multithreading and Grand Central Dispatch
和
How to Use Blocks
。
你现在有一个单例的对象作为管理专辑数据的入口。咋们更进一步来创建一个处理资料库数据持久化的类。
在
API
组中,使用
iOS\Cocoa Touch\Objective-C class
模板
创建一个新类,命名它为
PersistencyManager,
设置父类为
NSObject.
打开
PersistencyManager.h
在文件头部增加下面的导入语句:
#import "Album.h"
接下来,在
PersistenceManager.h
文件的
@interface
之后,增加下面的代码:
- (NSArray*)getAlbums;
- (void)addAlbum:(Album*)album atIndex:(int)index;
- (void)deleteAlbumAtIndex:(int)index;
上面是你需要处理专辑数据的方法的原型。
打开
PersistencyManager.m
文件,在
@implementation
行之前,增加下面的代码:
@interfacePersistencyManager () {
// an array of all albums
NSMutableArray *albums;
}
上面增加了一个类扩张(
class extension
),这是另外一个增加私有方法和变量以至于外部类不会看到它们的方式。这里,你申明了一个数组
NSMutableArry
来保存专辑数据。这个数组是可变的方便你增加和删除专辑。
现在在
PersistencyManager.m
文件中
@implementation
行之后增加如下代码:
- (id)init
{
self = [super init];
if (self) {
// a dummy list of albums
albums = [NSMutableArrayarrayWithArray:
@[[[Album alloc] initWithTitle:@"Best of Bowie" artist:@"David Bowie" coverUrl:@"http://www.coversproject.com/static/thumbs/album/album_david%20bowie_best%20of%20bowie.png" year:@"1992"],
[[Album alloc] initWithTitle:@"It's My Life" artist:@"No Doubt" coverUrl:@"http://www.coversproject.com/static/thumbs/album/album_no%20doubt_its%20my%20life%20%20bathwater.png" year:@"2003"],
[[Album alloc] initWithTitle:@"Nothing Like The Sun" artist:@"Sting" coverUrl:@"http://www.coversproject.com/static/thumbs/album/album_sting_nothing%20like%20the%20sun.png" year:@"1999"],
[[Album alloc] initWithTitle:@"Staring at the Sun" artist:@"U2" coverUrl:@"http://www.coversproject.com/static/thumbs/album/album_u2_staring%20at%20the%20sun.png" year:@"2000"],
[[Album alloc] initWithTitle:@"American Pie" artist:@"Madonna" coverUrl:@"http://www.coversproject.com/static/thumbs/album/album_madonna_american%20pie.png" year:@"2000"]]];
}
return self;
}
在
init
中,你用五条样例专辑填充数组。如果你不喜欢上面的专辑,你可以自由用你喜欢的专辑替换它们。
现在在
PersistencyManager.m
文件中增加下面的三个方法:
- (NSArray*)getAlbums
{
return albums;
}
- (void)addAlbum:(Album*)album atIndex:(int)index
{
if (albums.count >= index)
[albums insertObject:album atIndex:index];
else
[albums addObject:album];
}
- (void)deleteAlbumAtIndex:(int)index
{
[albums removeObjectAtIndex:index];
}
这些方法让你可以增加和删除专辑。
构建你的工程确保每个资源都可以被正确的编译。
这时候,你可能想知道
PersistencyManager
类来自哪里?因为它不是一个单例类。下一部分,我们将探究
LibraryAPI
和
PersistencyManager
之间的关系,那时候你将看到门面或者外观(
Facade
)模式。
原文出处:http://xmuzyq.iteye.com/blog/1942376