模型-视图-控制器(MVC)模式-设计模式之王
模型-视图-控制器(MVC)是Cocoa的构建块之一,毫无疑问它是使用最频繁的设计模式。它根据通用的角色去划分类,这样就使得类的
职责可以根据角色清晰的划分开来。
涉及到的三个角色如下:
Model:
模型保存应用程序的数据,定义了怎么去操作它。例如在本应用中模型就是Album类。
View:
视图是模型的可视化表示以及用户交互的控件;基本上来说,所有的UIView对象以及它的子类都属于视图。在本应用中AlbumView代表了视图。
Controller:
控制器是一个协调所有工作的中介者(Mediator)。它访问模型中的数据并在视图中展示它们,同时它们还监听事件和根据需要操作数据。你可以猜猜哪个类是控制器吗?它正是:ViewController。
一个MVC模式的好的实现也就意味着每一个对象都会被划分到上面所说的组中。
我们可以很好的用下图来描述通过控制器实现的视图到模型的交互过程:
模型会把任何数据的变更通知控制器,然后控制器更新视图数据。视图对象通知控制器用户的操作,控制器要么根据需要来更新模型,要么检索任何被请求的数据。
你可能在想为什么不能仅仅使用控制器,在一个类中实现视图和模型,这样貌似更加容易
所有的这些都归结于代码关注点分离以及复用。在理想的状态下,视图应该和模型完全的分离。如果视图不依赖某个实际的模型,那么视图就可以被复用来展示不同模型的数据。
举个例子来说,如果将来你打算加入电影或者书籍到你的资料库中,你仍然可以使用同样的AlbumView去显示电影和书籍数据。更进一步来说,如果你想创建一个新的与专辑有关联的工程,你可以很简单的复用Album类,因为它不依赖任何视图。这就是MVC的强大之处。
如何使用MVC模式
首先,你需要确保在你工程中的每个类是控制器,模型和视图中的一种,不要在一个类中组合两种角色的功能。到目前为止,你创建了一个Album类和AlbumView类,这样做挺好的。
其次,为了确保你能符合这种工作方法,你应该创建三个工程组(Project Group)来保存你的代码,每个工程组只存放一种类型的代码。
导航到”文件新建组(FileNewGroup)”(或者按下Command + Option + N),命名组为Model,重复同样的过程来创建View和Controller组。
现在拖动Album.h和Album.m去模型组,拖动AlbumView.h和AlbumView.m去视图组,最后拖动ViewController.h和ViewController.m到控制器组。
此时工程结构应该看起来和下图类似:
没有了之前所有文件都散落在各处,现在你的工程已经开起来好多了。显然你也可以有其它的组和类,但是本应用的核心包含在这三个类别中(Model,View,Controller)。
现在所有的组件都已经安排好了,你需要从某处获取专辑数据。你将创建一个贯穿于代码的管理数据的API-这也就代表将有机会去讨论下一个设计模式-单例(单态)模式。
单例(单态)模式
单例设计模式确保对于一个给定的类只有一个实例存在,这个实例有一个全局唯一的访问点。它通常采用懒加载的方式在第一次用到实例的时候再去创建它。
注意:苹果大量使用了此模式。例如:[NSUserDefaults standardUserDefaults],[UIApplication sharedApplication],[UIScreen mainScreen],[NSFileManager defaultManager],所有的这些方法都返回一个单例对象。
你很可能会想为什么这么关心是否一个类有多个实例?毕竟代码和内存都是廉价的,对吗?
有一些情况下,只有一个实例显得非常合理。举例来说,你不需要有多个Logger的实例,除非你想去写多个日志文件。或者一个全局的配置处理类:实现线程安全的方式访问共享实例是容易的,比如一个配置文件,有好多个类同时修改这个文件。
如何使用单例模式
首先来看看下面的图:
上面的图描述了一个有单一属性(它就是单一实例)和sharedInstance,init两个方法的类。客户端第一次发送sharedInstance消息的时候,instance属性尚未被初始化,所以此时你需要创建一个新的实例,然后返回它的引用。
当你下一次调用sharedInstance的时候,instance不需要任何初始化可以立即返回。这个逻辑保证总是只有一个实例。你接下来将用这个模式来创建一个管理所有专辑数据的类。你将注意到工程中有一个API的组,在这个组里你可以放入给你应用提供服务的所有类。在此组中,用IOSCocoa TouchObjective-C class模板创建一个新类,命名它为LibraryAPI,设置父类为NSObject.
打开LibraryAPI.h,用如下代码替换它的内容:
1
2
3
4
5
|
@interfaceLibraryAPI : NSObject + (LibraryAPI*)sharedInstance; @end |
现在打开LibraryAPI.m,在@implementation那一行后面插入下面的方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
+ (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组中,使用iOSCocoa TouchObjective-C class模板创建一个新类,命名它为PersistencyManager,设置父类为NSObject.
打开PersistencyManager.h在文件头部增加下面的导入语句:
#import “Album.h”
接下来,在PersistenceManager.h文件的@interface之后,增加下面的代码:
1
2
3
|
- (NSArray*)getAlbums; - (void)addAlbum:(Album*)album atIndex:(int)index; - (void)deleteAlbumAtIndex:(int)index; |
上面是你需要处理专辑数据的方法的原型。
打开PersistencyManager.m文件,在@implementation行之前,增加下面的代码:
1
2
3
4
|
@interfacePersistencyManager () { // an array of all albums NSMutableArray *albums; } |
上面增加了一个类扩张(class extension),这是另外一个增加私有方法和变量以至于外部类不会看到它们的方式。这里,你申明了一个数组NSMutableArry来保存专辑数据。这个数组是可变的方便你增加和删除专辑。
现在在PersistencyManager.m文件中@implementation行之后增加如下代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
- (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文件中增加下面的三个方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
- (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)模式。
单例模式(Singleton)
概念:整个应用或系统只能有该类的一个实例
在iOS开发我们经常碰到只需要某类一个实例的情况,最常见的莫过于对硬件参数的访问类,比如UIAccelerometer.这个类可以帮助我们获得硬件在各个方向轴上的加速度,但是我们仅仅需要它的一个实例就够了,再多,只会浪费内存。
所以苹果提供了一个UIAccelerometer的实例化方法+sharedAccelerometer,从名字上我们也能看出此方法让整个应用共享一个UIAccelerometer实例(PS:iOS 的开放中,我们往往能从方法名中就了解这个方法的作用),它内部的如何实现我们暂且不谈,先来看看还有哪些类同样使用了单例模式。
- UIApplication类提供了 +sharedAPplication方法创建和获取UIApplication单例
- NSBundle类提供了 +mainBunle方法获取NSBundle单例
- NSFileManager类提供了 +defaultManager方法创建和获得NSFileManager单例。(PS:有些时候我们得放弃使用单例模式,使用-init方法去实现一个新的实例,比如使用委托时)
- NSNotificationCenter提供了 +defaultCenter方法创建和获取NSNotificationCenter单例(PS:该类还遵循了另一个重要的设计模式:观察者模式)
- NSUserDefaults类提供了 +defaultUserDefaults方法去创建和获取NSUserDefaults单例
等等,苹果的SDK中大量的遵循此设计模式,那么它的内部是如何实现的呢?
首先给大家介绍一下GCD技术,是苹果针对于多核CPU的多任务解决方案。你不需要了解更多,只需要知道是一组基于C语言开发的API(详细内容可以看一下唐巧前辈的这篇博文:http://blog.devtang.com/blog/2012/02/22/use-gcd/ )。GCD提供了一个dispatch_once函数,这个函数的作用就是保证block(代码块:暂时理解为一个跟函数相近的东西,具体可以参照这篇文章:http://blog.csdn.net/enuola/article/details/8674063 )里的语句在整个应用的生命周期里只执行一次。
OK,接下来就给出一个使用了单例模式新建和获取实例的类模版,代码如下:
//Singleton.h
@interface Singleton : NSObject
+ (Singleton *)sharedSingleton; <1>
@end
/***************************************************************/
//Singleton.m
#import "Singleton.h"
@implementation Singleton
static Singleton *sharedSingleton = nil;<2>
+ (Singleton *)sharedSingleton{
static dispatch_once_t once;<3>
dispatch_once(&once,^{
sharedSingleton = [[self alloc] init];<4>
//dosometing
});
return sharedSingleton;<5>
}
上述代码中有5小步,解释如下:
- 声明一个可以新建和获取单个实例对象的方法
- 声明一个static类型的类变量
- 声明一个只执行一次的任务
- 调用dispatch_once执行该任务指定的代码块,在该代码块中实例化上文声明的类变量
- 返回在整个应用的生命周期中只会被实例化一次的变量
OK,这就是iOS开发中单例模式的机制,下面我们就看看如何在实际开发中使用此模式?(PS:为了尽可能的突出核心内容,我们会对设计中的其他模式或内容一笔带过)
假如我们需要在iOS应用中实现分层的架构设计,即我们需要把数据的持久层,展示层,和逻辑层分开。为了突出重点,我们直接把目光转到持久层,而根据MVC的设计模式,我们又可以把持久层细分为DAO层(放置访问数据对象的四类方法)和Domain层(各种实体类,比如学生),这样就可以使用DAO层中的方法,配合实体类Domain层对数据进行清晰的增删改查。那么我们如何设计呢?
从使用者的角度看,我们期望获得DAO层的类实例,然后调用它的增删改查四大方法。可是这个类实例,我们似乎只需要一个就足够了,再多的话不利于管理且浪费内存。OK,我们可以使用单例模式了,代码如下:
.h文件:
//StudentDAO.h
@interface StudentDAO:NSObject
@property (nonatomic,strong) NSMutaleArray *StudentsInfo;
+ (StudentDAO *)sharedStudentDAO;
-(int) create:(Student*)student;
-(int) remove:(Student*)student;
-(int) modify:(Student*)student;
-(NSMutaleArray) findAll;
@end
.m文件:
//StudentDAO.m
#import "StudentDAO.h"
#import "Student.h"
@implementation StudentDAO
static StudentDAO *studentDao = nil;
+ (StudentDAO)sharedStudentDAO{
static dispatch_once_t once;
dispatch_once(&once,^{
Student *student1 = [[Student alloc]init];
student1.name = "MexiQQ";
student1.studentNum = "201200301101";
Student *student2 = [[Student alloc]init];
student2.name = "Ricardo_LI";
student2.studentNum = "201200301102";
studentDao = [[self alloc] init];
studentDao._StudentsInfo = [[NSMutaleArray alloc]init];
[studentDao._StudentsInfo addObject:student1];
[studentDao._StudentsInfo addObject:student2];
});
return studentDao;
}
//插入的方法
-(int)create:(Student*)stu{
[self._StudentsInfo addObject:stu];
return 0;
}
//删除的方法
-(int)remove:(Student*)stu{
for(Student* s in self._StudentsInfo){
if([stu.studentNum isEqual:s.studentNum]){
[self._StudentsInfo removeObject:s]
break;
}
}
}
-(int)modify...... //省略不写
-(NSMutaleArray)findAll...... //省略不写
上述例子不难理解,其中用到的Student类我这里就不给出了,只是一个含有姓名和学号属性的实体类。
观察者模式
概念:一个对象状态改变,通知正在对他进行观察的对象,这些对象根据各自要求做出相应的改变
图例:
如图所示:操作对象向被观察者对象投送消息,使得被观察者的状态得以改变,在此之前已经有观察者向被观察对象注册,订阅它的广播,现在被观察对象将自己状态发生改变的消息广播出来,观察者接收到消息各自做出应变。
OK,我们先来看看在苹果的Cocoa Touch框架中有谁使用了观察者模式:
通知(notification)机制
原理图如下:
如图所示,在通知机制中对某个通知感兴趣的所有对象都可以成为接受者。首先,这些对象需要向通知中心(NSNotificationCenter)发出addObserver:selector:name:object:消息进行注册,在投送对象投送通知送给通知中心时,通知中心就会把通知广播给注册过的接受者。所有的接受者不知道通知是谁投送的,不去关心它的细节。投送对象和接受者是一对多的关系。接受者如果对通知不再关注,会给通知中心发送removeObserver:name:Object:消息解除注册,以后不再接受通知。
(ps:这段话内容摘抄自关东升先生的文章)
OK,我们试着去使用一下通知机制:
新建一个Single view Project,对项目中的文件做以下修改:
AppDelegate.m
- (void)applicationDidEnterBackground:(UIApplication *)application {
[[NSNotificationCenter defaultCenter]postNotificationName:@"APPTerminate" object:self];
}
ViewController.m
//
// ViewController.m
// TestNotification
//
// Created by liwenqian on 14-10-18.
// Copyright (c) 2014年 liwenqian. All rights reserved.
//
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//注意此处的selector有参数,要加冒号
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(doSomething:) name:@"APPTerminate" object:nil];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
[[NSNotificationCenter defaultCenter]removeObserver:self];
// Dispose of any resources that can be recreated.
}
#pragma mark -处理通知
-(void)doSomething:(NSNotification*)notification{
NSLog(@"收到通知");
}
@end
如上所示,对模版项目的两个文件的方法或整个文件做出修改,Command+R运行你的APP,再按下Home键(Command+H),会发现打印出一行收到通知的文字,如下:
在APP退到后台时,发出广播,而viewController因为时观察者,收到广播,执行doSomething方法,打印出收到广播的文字。
KVO(Key-Value-Observing)机制
原理图如下:
如图所示:
该机制下观察者的注册是在被观察者的内部进行的,不同于通知机制(由观察者自己注册),需要被观察者和观察者同时实现一个协议:NSKeyValueObserving,被观察者通过addObserver:forKeypath:options:context方法注册观察者,以及要被观察的属性。
新建一个single view project,同时新建一个继承自NSObject的TestWatche类:文件结构如下图:
对文件进行如下修改:
AppDelegate.h
//
// AppDelegate.h
// TestNotification
//
// Created by liwenqian on 14-10-18.
// Copyright (c) 2014年 liwenqian. All rights reserved.
//
#import <UIKit/UIKit.h>
#import <CoreData/CoreData.h>
#import "TestWatche.h"
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
@property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
@property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;
@property (strong,nonatomic) NSString *state;
@property (strong,nonatomic) TestWatche *watcher;
- (void)saveContext;
- (NSURL *)applicationDocumentsDirectory;
@end
AppDelegate.m 对如下方法做出修改
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
self.watcher = [TestWatche alloc];
[self addObserver:self.watcher forKeyPath:@"state" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:@"pass content"];
self.state = @"launch";
return YES;
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
self.state = @"backgroud";
}
TestWatche.m(由于继承自NSObject,而NSObject已实现了NSKeyValueObserving协议,所以不需要做声明)
//
// TestWatche.m
// TestNotification
//
// Created by liwenqian on 14-10-18.
// Copyright (c) 2014年 liwenqian. All rights reserved.
//
#import "TestWatche.h"
@implementation TestWatche
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
NSLog(@"state change:%@",change);
}
@end
OK,Command+B Command+R Command+H看看你的应用输出了什么,如果你的操作无误的话,会显示如下内容:
委托模式
个人认为委托模式大多数人解释的复杂了,其实就像是java中的接口,类可以实现或不实现协议(接口)中的方法。通过此种方式,达到最大的解耦目的,方便项目的扩展。不过你需要设置应用的委托对象,以确定协议中的方法为谁服务。
拿最常用的UITableViewDelegate UITableViewDataSource来举例:
实现一个页面有一个UItableView,UItableView的每一栏(cell)的数据由我们指定,那么我们该如何做呢?苹果也自然想到了这一点,于是定义了一个接口,这个接口有许多的方法,只需要我们把要服务的对象传进去,就可以使用这些方法了,这个接口就是委托和协议。而UITableViewDelegate 和 UITableViewDataSource 就是专为UITableView而写的委托和协议。用法如下:
ViewController.h
//
// ViewController.h
// RecipeBookMe
//
// Created by liwenqian on 14-9-10.
// Copyright (c) 2014年 liwenqian. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
@property (nonatomic, strong) IBOutlet UITableView *mytableView;
@end
ViewController.m
//
// ViewController.m
// RecipeBookMe
//
// Created by liwenqian on 14-9-10.
// Copyright (c) 2014年 liwenqian. All rights reserved.
//
#import "ViewController.h"
#import "DetailViewController.h"
#import "Recipe.h"
#import "RecipeTableCellTableViewCell.h"
@interface ViewController ()
@end
@implementation ViewController{
NSArray *recipes;
}
@synthesize mytableView;
- (void)viewDidLoad {
[super viewDidLoad];
// Initialize the recipes array
Recipe *recipe1 = [Recipe new];
recipe1.name = @"Egg Benedict";
recipe1.prepTime = @"30 min";
recipe1.image = @"egg_benedict.jpg";
recipe1.ingredients = [NSArray arrayWithObjects:@"2 fresh English muffins", @"4 eggs", @"4 rashers of back bacon", @"2 egg yolks", @"1 tbsp of lemon juice", @"125 g of butter", @"salt and pepper", nil];
Recipe *recipe2 = [Recipe new];
recipe2.name = @"Mushroom Risotto";
recipe2.prepTime = @"30 min";
recipe2.image = @"mushroom_risotto.jpg";
recipe2.ingredients = [NSArray arrayWithObjects:@"1 tbsp dried porcini mushrooms", @"2 tbsp olive oil", @"1 onion, chopped", @"2 garlic cloves", @"350g/12oz arborio rice", @"1.2 litres/2 pints hot vegetable stock", @"salt and pepper", @"25g/1oz butter", nil];
recipes = [NSArray arrayWithObjects:recipe1, recipe2, nil];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
/*--------------------------------------------------------------------*/
//定义UITableview的栏目数量
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [recipes count];
}
//定义UITableviewCell的显示内容
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CoustomerTableIdentifier = @"RecipeTableCellTableViewCell";
RecipeTableCellTableViewCell *cell =(RecipeTableCellTableViewCell *) [tableView dequeueReusableCellWithIdentifier:CoustomerTableIdentifier];
if (cell == nil) {
cell = [[RecipeTableCellTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CoustomerTableIdentifier];
}
recipe = [recipes objectAtIndex:indexPath.row];
cell.nameLabel.text = recipe.name;
cell.prepTimeLabel.text= recipe.prepTime;
cell.thumbnailImageView.image = [UIImage imageNamed:recipe.image];
return cell;
}
//点击每一栏执行跳转时的处理
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:@"showRecipeDetail"]) {
NSIndexPath *indexPath = nil;
Recipe *recipe = nil;
indexPath = [self.mytableView indexPathForSelectedRow];
recipe = [recipes objectAtIndex:indexPath.row];
DetailViewController *destViewController = segue.destinationViewController;
destViewController.recipe = [recipes objectAtIndex:indexPath.row];
}
}
//定义cell的高度
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 71;
}
/*--------------------------------------------------------------------*/
@end
如上所示,两条/------/注释间的方法全部来自于委托和协议。利用委托和协议,你可以把主要精力放到逻辑业务上,将数据绑定和事件处理交给委托和协议去完成。
工厂模式
工厂模式我的理解是:他就是为了创建对象的
创建对象的时候,我们一般是alloc一个对象,如果需要创建100个这样的对象,如果是在一个for循环中还好说,直接一句alloc就行了,但是事实并不那么如意,我们可能会在不同的地方去创建这个对象,那么我们可能需要写100句alloc 了,但是如果我们在创建对象的时候,需要在这些对象创建完之后,为它的一个属性添加一个固定的值,比方说都是某某学校的学生,那么可能有需要多些100行重复的代码了,那么,如果写一个-(void)createObj方法,把创建对象和学校属性写在这个方法里边,那么就是会省事很多,也就是说我们可以alloc 创建对象封装到一个方法里边,直接调用这个方法就可以了,这就是简单工厂方法
但是工厂方法也有它的限制:
1.要创建的类必须拥有同一个父类
2.要创建的类在100个不同的地方所调用的方法必须一样
工厂方法
工厂方法模式:定义创建对象的接口,让子类决定实例化哪一个类。工厂方法使得一个类的实例化延迟到其子类。
工厂方法的使用场景
- 编译时无法准确预期要创建的对象的类;
- 类想让子类决定在运行时创建什么;
- 类有若干辅助类为其子类,而你想将返回哪个子类这一信息局部话;
工厂方法在CocoaTouch中的应用
在Objective-C中两步对象创建法[[SomeClass alloc] init].但是还有一些便利的创建方法。例如,NSNumber有很多numberWith*方法;其中有两个是numberWithBool:和numberWithChar:。它们是类方法,也就是说我们向NSNumber发送[[NSNumber numberWithBool:bool]]与[[NSNumber numberWithChar:char]],以获得与传入参数同类型的各种NSNumber实例。与如何创建NSNumber的具体子类型的实例有关的细节,都有NSNumber的类工厂方法负责。[[NSNumber numberWithBool:bool]]的情况是,方法接受值bool,并把NSNumber的内部子类的一个实例初始化,让它能够反应传入的值。
工厂方法在Objective-C中的实现
1、定义通过工厂方法创建的类的统一父类(例如:Animal)和通过工厂方法创建的各个类(例如:Dog、Cat等),并为各个子类提供初始化细节。
//通过工厂方法创建类的实现举例
- (id)initWithName:(NSString*)name{
if(self = [super init]){
self.name = name;
//添加初始化相关细节
...
}
return self;
}
2、定义生成器父类(例如:AnimalGenerator)和各个生成器子类(例如:DogGenerator、CarGenerator)。
3、生成器父类中创建工厂方法、各个生成器子类中重载该方法
//AnimalGenerator中工厂方法
- (Animla*)animalWithName:(NSString*)name{
return [[Animal alloc] initWithName:name];
}
//DogGenerator对AnimalGenerator中工厂方法的重载
- (Animal*)animalWithName:(NSString*)name{
return [[Dog alloc] initWitName:name];
}
4、使用
DogGenerator *dogGenerator = [[DogGenerator alloc] init];
//不同生成器创建不同的动物
Animal *dog = [[dogGenerator animalWithName:@"小七"]];
在iOS中有一些基本的设计模式:
- delegation
- protocol
- model-vew-controller
- target-action
- notification
- declared properties
下面分别介绍一下各个设计模式的用途。
Delegation
这是Apple的权威解释。
delegation,委托模式(另外有个常用的proxy模式,二者的区别是代理模式一般要更严格,实现相同的接口,委托只是引用被委托对象),是简单的强大的模式,可让一个对象扮演另外对象的行为。委托对象保持到另外对象的引用,并在适当的时候发消息给另外对象。委托对象可以在发送消息的时候做一些额外的事情。
在cocoa框架中的委托模式,委托对象往往是框架中的对象,被委托对象是自定义的controller对象。委托对象保持一个到被委托对象的弱引用。
在该文档中所举的例子是mac下开发的示例。
这里被委托对象是NSWindow类的一个实例,该类声明了协议(protocol),其中有个方法是windowShouldClose,当用户点击窗口的关闭按钮的时候,窗口对象将发送windowShouldClose消息给代理对象(windowDelegate),询问是否确认关闭窗口。代理对象返回一个bool值,因此控制了窗口对象的行为。
在iOS中也有类似的示例,在创建的项目中,classes目录中会有xxAppDelegate,这就是委托类。在该委托类实例中,引用了UIWindow和Controller。可以类似上面mac示例控制被引用对象的行为。
Protocol
可以认为是java中的接口(interface)。在iOS中有两种protocol:
- 正式的protocol:通过@interface声明,要求子类实现方法,可以强制实现(@required)或者可选(@optional),正式的protocol是对objc语言的扩展;
- 非正式的protocol是NSObject的一个category,category是objc语言的一个特性,可以让你为一个类增加方法而不需要实现子类。非正式protocol实现方法是可选的。在可选的protocol方法未引入objc 2.0以前,非正式的protocol是在foundation和appkit类中实现委托的基本方式
Model-View-Controller
MVC设计模式大家应该很熟悉了,不多说了。
Target-Action
target-action是个设计模式。对象保持必要的信息,当事件发生的时候发送消息给其他对象。所保持的信息有两部分数据组成:
- action selector,定义要调用的方法名称标识;
- target,接收消息的对象。
当被称作action message的事件发生,将向target发送action selector定义的方法消息。
target-action模式一般用于自定义的controller按照应用规范定义的方式处理action message。
Notification
一个notification,即一个通知,是一个消息,是用于通知一到多个观察者对象程序当前有一个事件发生。这里,接收通知的一方叫观察者,observer。实际上是观察者模式。
这里,发送通知的对象,并不知道谁会收到这个通知。这样通知者和观察者之间松散耦合。