一 iOS中都有什么设计模式?
1.代理模式
2.观察者模式
3.MVC模式
4.单例模式
5.策略模式
6.工厂模式
二 各个设计模式的作用?
(一)代理模式
在观察者模式中,一个对象任何状态的变更都会通知另外的对改变感兴趣的对象。这些对象之间不需要知道彼此的存在,这其实是一种松耦合的设计。当某个属性变化的时候,我们通常使用这个模式去通知其它对象。
此模式的通用实现中,观察者注册自己感兴趣的其它对象的状态变更事件。当状态发生变化的时候,所有的观察者都会得到通知。苹果的推送通知(Push Notification)就是一个此模式的例子。
如果你要遵从MVC模式的概念,你需要让模型对象和视图对象在不相互直接引用的情况下通信。这正是观察者模式的用武之地。
Cocoa通过通知(Notifications)和Key-Value Observing(KVO)来实现观察者模式。
在cocoa框架中的Delegate模式中,委托人往往是框架中的对象(视图中的控件、表视图神马的),代理人往往是视图控制器对象。
在我们这个例子中UITableView是委托人,代理人首先得满足一个条件:就是在.h文件中申明它拥有代理资格:
@interface WhateverViewController < UITableViewDelegate > @end
红色的表示这个视图控制器拥有UITableView的代理资格。
其次,在.m文件中定义委托人可以让代理人去代替做的事情:
//视图控制器来代办应该有多少个节 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return [NSArray count]; }
//视图控制器来代办某个节应该有多少行 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [[NSArray objectAtIndex:section]count]; } // 视图控制器来代办负责每个栏格的外观 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *CellIdentifier = @"Cell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; if (cell == nil) { cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease]; } cell.textField.text = [NSArray objectAtIndex:indexPath.row]; return cell; } //负责当栏格被点击后需要触发的事件 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { AnotherViewController *anotherViewController = [[AnotherViewController alloc] initWithNibName:@"AnotherView" bundle:nil]; [self.navigationController pushViewController:anotherViewController]; [anotherViewController release]; } // 这个是可选的,视图控制器勤快我就帮你代办,不勤快我就可以不帮你办这事儿,(提供哪些个行可以被编辑) - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath { return YES; }
// 对特定编辑风格进行操作 - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { if (editingStyle == UITableViewCellEditingStyleDelete) { [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:YES]; } else if (editingStyle == UITableViewCellEditingStyleInsert) { } }
// 可选,对那些被移动栏格作特定操作 - (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath { }
// 对那些可以移动的行返回YES - (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath { // 如果不想让栏格移动,就返回NO return YES; }
好了,当这个委托人需要办这些事时,代理人自己就站出来帮忙办了。这就是ios中的Delegate模式。
二、自定义的delegate模式
@interface A:UIView id transparendValueDelegate; @property(nomatic, retain) id transparendValueDelegate; @end @implementation A @synthesize transparendValueDelegate -(void)Call { NSString* value = @"你好"; [transparendValueDelegate transparendValue: value]; } @end
@interface B:UIView
NSString* value;
@end
@implementation B
-(void)transparendValue:(NSString*)fromValue
{
value = fromValue;
NSLog(@"%@ ,我是B",value);
}
@end
使用时:
A* a = [[A alloc] init]; B* b = [[B alloc] init]; a. transparendValueDelegate = b;//设置A代理委托对象为B [a Call];
这样就会输出:
你好,我是B
委托模式关键就在于一个“被”字。这个B是很被动的,随时就会被你A Call一下。
三、为什么会有delegate模式
换句话说,它可以用来解决神马问题?
当一个类的某些功能需要被别人来实现,但是既不明确是些什么功能,又不明确谁来实现这些功能的时候,委托模式就可以派上用场。
例如你可以再写个C类,实现-(void)transparendValue:(NSString*)fromValue {NSLog(@"%@ ,我是C",value); }也是完全可以的。换谁来,只要它实现了这个方法,我就可以委托它来做这个事。
说到底一切都是为了使类之间的耦合性更松散。好的代码应该对扩展开放,对修改关闭。
总结来自http://blog.sina.com.cn/s/blog_b638dc89010192qu.html
(二)观察者模式
在软件开发中,无论是那种高级语言中总会伴随着一些最为常用的设计模式,即便就如iOS开发中与我们打交道最多的无非就是单例模式、观察者模式和工厂模式了,当然了其他的设置模式也同样存在在编程的很多地方。下面就就让我们简单的了解下观察者模式吧!
观察者模式本质上时一种发布-订阅模型,用以消除具有不同行为的对象之间的耦合,通过这一模式,不同对象可以协同工作,同时它们也可以被复用于其他地方Observe
r从Subject
订阅通知,ConcreteObserver
实现重现ObServer
并将其重载其update
方法。一旦SubJect的实例需要通知Observer
任何新的变更,Subject
会发送update
消息来通知存储在其内部类中所注册的Observer
、在ConcreteObserver
的update
方法的实际实现中,Subject
的内部状态可被取得并进行后续处理。其类图如下:
![](https://i-blog.csdnimg.cn/blog_migrate/11d1c516a31085df7aacfc7c5999b6f9.webp?x-image-process=image/format,png)
由上面我们可以发现观察者模式无非在是定义对象间的一种一对多的依赖关系,并且当一个对象的状态发生改变的时候,所有依赖于它的对象都会得到通知且自动更新。即如果Subject
允许其他观察者(实现了观察者接口的对象)对这个Subject
的改变进行请阅,当Subject
发送了变化,那么Subject
会将这个变化发送给所有的观察者,观察者就能对Subject
的变化做出更新。其时序图如下
![](https://i-blog.csdnimg.cn/blog_migrate/99e1baad624224490813225cc8b0b763.webp?x-image-process=image/format,png)
通过上面的观察我们可以发现如果用N个Observer
来拓展Subject
的行为,这些Observer
具有处理存储在Subject中的信息的特定实现,这样也就实现了前面所说的消除不同对象间的耦合的功能了。
那么了解了这些我们可能就会更像了解下我们在什么时候才会去使用观察者模式呢?
- 当需要将改变通知所有的对象时,而你又不知道这些对象的具体类型
- 改变发生在同一个对象中,并需要改变其他对象将相关的状态进行更新且不知道有多少个对象。
而同样的在我们日常的开发中在Cocoa Touch
框架中的的两种经常打交道的技术KVO
与通知都实现了观察者模式,所以下面我们讨论的重点也就是基于这两个方面的。
通知
在之前的博文中曾经简单的提到过一些通知的基础使用方法,所以一些基本的使用方法再次就不赘述。言归正传,在Cocoa Touch框架中NSNotificationCenter
和NSNotification
对象实现了一对多的模型。通过NSNotificationCenter
可以让对象之间进行通讯,即便这些对象之间并不认识。下面我们来看下NSNotificationCenter
发布消息的方法:
NSNotification * subjectMessage = [ NSNotification notificationWithName:@"subjectMessage" object: self];
NSNotificationCenter * notificationCenter = [ NSNotificationCenter defaultCenter]; [notificationCenter postNotification:subjectMessage];
通过上面的代码我们创建了一个名为subjectMessage
的NSNotification
对象,然后通过notificationCenter
来发布这个消息。通过向NSNotificationCenter
类发送defaulCenter
消息,可以得到NSNotificationCenter
实例的引用。每个进程中只有一个默认的通知中心,所以默认的NSNotificationCenter
是个单例对象。如果有其他观察者定于了其对象的相关事件则可以通过以下的方法来进行操作:
NSNotificationCenter * notificationCenter1 = [ NSNotificationCenter defaultCenter];
[notificationCenter addObserver: self selector: @selector(update:) name:@"subjectMessage" object: nil ];
经过以上步骤我们已经向通知中心注册了一个事件并且通过selector制定了一个方法update:
下面我们可以实现以下这个方法
- (void)update:(NSNotification*)notification{
if ([[notification name] isEqualToString:@"subjectMessage"]) {
NSLog(@"%@",@"猴子派来的救兵去哪了?"); } }
当然最后如果我们需要对监听进行销毁
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
了解过通知之后我们来看一下KVO
KVO是Cocoa
提供的一种称为键值观察的机制,对象可以通过它得到其他对象特定属性的变更通知。而这个机制是基于NSKeyValueObserving
非正式些,Cocoa
通过这个协议为所有遵循协议的对象提供了一种自动化的属性监听的功能。
虽然通知
和KVO
都可以对观察者进行实现,但是他们之间还是略有不同的,由上面的例子我们可以看出通知是由一个中心对象为所有观察者提供变更通知,主要是广义上关注程序事件,而KVO
则是被观察的对象直接想观察者发送通知,主要是绑定于特定对象属性的值。下面我们通过一个简单的例子来了解下他的一些是使用方法
首先我们有Hero
这个模型
@property (nonatomic,copy) NSString * name;
@property (nonatomic,copy) NSString * title; @property (nonatomic,assign) NSUInteger age;
在控制其中我们将其初始化并赋值
self.hero = [[Hero alloc] init];
self.hero.name = @"赵云"; self.hero.title = @"将军"; self.hero.age = 87;
现在我们的这个对象基本有值了,那么我们将这个对象的name
监听下他的改变
[self.hero addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
触发通知并将值改变
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
self.hero.name = @"张飞"; }
在制定的回调函
数中,处理收到的更改通知
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{ if([keyPath isEqualToString:@"name"]) { NSLog(@"赋值后--%@",self.hero.name); NSLog(@"新的值--%@",change[@"new"]); NSLog(@"以前的值--%@",change[@"old"]); } }
回调
打印如下:
![](https://i-blog.csdnimg.cn/blog_migrate/d0f64379868d66b0758e6737b83c80e7.webp?x-image-process=image/format,png)
最后注销
观察者
- (void)dealloc{
[self.hero removeObserver:self forKeyPath:@"name"]; }
到了这里观察者模式中常用的KVO
及通知
的内容就到这里,不过要知道这里谈及的只是最基础的用法,后面我们可能还是有更加深入的探究,或者在后续中可能还会对比iOS中的代理以及Block
来探寻下iOS中的消息传递机制
,再或者像Swift
中的didSet
、willSet
的属性监听的方法,这些都是很好玩的内容,不是么?
(三)MVC模式
MVC根据角色划分类,涉及到三个角色:
Model:模型保存应用程序的数据。
Controller:控制器是一个协调所有工作的中介者。它访问模型中的数据并在视图中展示它们,同时它们还监听事件和操作数据。
一个MVC模式的好的实现也就意味着每一个对象都会被划分到上面所说的组中。
我们可以很好的用下图来描述通过控制器实现的视图到模型的交互过程:
模型会把任何数据的变更通知控制器,然后控制器更新视图数据。视图对象通知控制器用户的操作,控制器要么根据需要来更新模型,要么检索任何被请求的数据。
你可能在想为什么不能仅仅使用控制器,在一个类中实现视图和模型,这样貌似更加容易?
所有的这些都归结于代码关注点分离以及复用。在理想的状态下,视图应该和模型完全的分离。如果视图不依赖某个实际的模型,那么视图就可以被复用来展示不同模型的数据。
举个例子来说,如果将来你打算加入电影或者书籍到你的资料库中,你仍然可以使用同样的AlbumView去显示电影和书籍数据。更进一步来说,如果你想创建一个新的与专辑有关联的工程,你可以很简单的复用Album类,因为它不依赖任何视图。这就是MVC的强大之处。
(四)单例模式
单例设计模式确保对于一个给定的类只有一个实例存在,这个实例有一个全局唯一的访问点。它通常采用懒加载的方式在第一次用到实例的时候再去创建它。
注意:苹果大量使用了此模式。例如:[NSUserDefaults standardUserDefaults], [UIApplication sharedApplication], [UIScreen mainScreen], [NSFileManager defaultManager],所有的这些方法都返回一个单例对象。
你很可能会想为什么这么关心是否一个类有多个实例?毕竟代码和内存都是廉价的,对吗?
有一些情况下,只有一个实例显得非常合理。举例来说,你不需要有多个Logger的实例,除非你想去写多个日志文件。或者一个全局的配置处理类:实现线程安全的方式访问共享实例是容易的,比如一个配置文件,有好多个类同时修改这个文件。
(五)策略模式
1.概述
在软件开发中也常常遇到类似的情况,实现某一个功能有多种算法或者 策略,我们可以根据环境或者条件的不同选择不同的算法或者策略来完成该功能 。如查找、排序等,一种常用的方法是硬编码(Hard Coding)在一个类中,如需要提供多种查找算法,可以将这些算法写到一个类中,在该类中提供多个方法,每一个方法对应一个具体的查找算法;当然也可以将这些查找算法封装在一个统一的方法中,通过if…else…或者 case 等条件判断语句来进行选择。这两种实现方法我们都可以称之为硬编码,如果需要增加一种新的查找算法,需要修改封装算法类的源代码;更换查找算法,也需要修改客户端调用代码。在这个算法类中封装了大量查找算法, 该类代码将较复杂,维护较为困难。如果我们将这些策略包含在客户端 ,这种做法更不可取,将导致客户端程序庞大而且难以维护,如果存在大量可供选择的算法时问题将变得更加严重。
例子1:一个菜单功能能够根据用户的“皮肤”首选项来决定是否采用水平的还是垂直的排列形式。同事可以灵活增加菜单那的显示样式。
例子2:出行旅游:我们 可以有几个策略可以考虑:可以骑自行车,汽车,做火车,飞机。每个策略都可以得到相同的结果,但是它们使用了不同的资源。选择策略的依据是费用,时间,使用工具还有每种方式的方便程度 。
2.问题
如何让算法和对象分开来,使得算法可以独立于使用它的客户而变化?
3.解决方案
策略模式: 定 义一系列的算法,把每一个算法封装起来, 并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。 也称为 政策模式 (Policy) 。( Definea family of algorithms,encapsulate each one, andmake them interchangeable. Strategy lets the algorithmvary independently from clients that use it. )
策略模式把对象本身和运算规则区分开来,其 功能非常强大,因为这个设计模式本身的核心思想就是面向对象编程的多形性的思想。
4.适用性
当存在以下情况时使用Strategy模式
1)• 许多相关的类仅仅是行为有异。 “策略”提供了一种用多个行为中的一个行为来配置一个类的方法。即一个系统需要动态地在几种算法中选择一种。
2)• 需要使用一个算法的不同变体。例如,你可能会定义一些反映不同的空间 /时间权衡的算法。当这些变体实现为一个算法的类层次时 ,可以使用策略模式。
3)• 算法使用客户不应该知道的数据。可使用策略模式以避免暴露复杂的、与算法相关的数据结构。
4)• 一个类定义了多种行为 , 并且这些行为在这个类的操作中以多个条件语句的形式出现。将相关的条件分支移入它们各自的Strategy类中以代替这些条件语句。
5.结构
6.效果
Strategy模式有下面的一些优点:
1) 相关算法系列
Strategy类层次为Context定义了一系列的可供重用的算法或行为。 继承有助于析取出这些算法中的公共功能。
2) 提供了可以替换继承关系的办法
: 继承提供了另一种支持多种算法或行为的方法。你可以直接生成一个Context类的子类,从而给它以不同的行为。但这会将行为硬行编制到 Context中,而将算法的实现与Context的实现混合起来,从而使Context难以理解、难以维护和难以扩展,而且还不能动态地改变算法。最后你得到一堆相关的类 , 它们之间的唯一差别是它们所使用的算法或行为。 将算法封装在独立的Strategy类中使得你可以独立于其Context改变它,使它易于切换、易于理解、易于扩展。
3) 消除了一些if else条件语句
:Strategy模式提供了用条件语句选择所需的行为以外的另一种选择。当不同的行为堆砌在一个类中时 ,很难避免使用条件语句来选择合适的行为。将行为封装在一个个独立的Strategy类中消除了这些条件语句。含有许多条件语句的代码通常意味着需要使用Strategy模式。4) 实现的选择
Strategy模式可以提供相同行为的不同实现。客户可以根据不同时间 /空间权衡取舍要求从不同策略中进行选择。
Strategy模式缺点 :
1)客户端必须知道所有的策略类,并自行决定使用哪一个策略类
: 本模式有一个潜在的缺点,就是一个客户要选择一个合适的Strategy就必须知道这些Strategy到底有何不同。此时可能不得不向客户暴露具体的实现问题。因此仅当这些不同行为变体与客户相关的行为时 , 才需要使用Strategy模式。
2 ) Strategy和Context之间的通信开销
:无论各个ConcreteStrategy实现的算法是简单还是复杂, 它们都共享Strategy定义的接口。因此很可能某些 ConcreteStrategy不会都用到所有通过这个接口传递给它们的信息;简单的 ConcreteStrategy可能不使用其中的任何信息!这就意味着有时Context会创建和初始化一些永远不会用到的参数。如果存在这样问题 , 那么将需要在Strategy和Context之间更进行紧密的耦合。3 )策略模式将造成产生很多策略类
:可以通过使用享元模式在一定程度上减少对象的数量。 增加了对象的数目 Strategy增加了一个应用中的对象的数目。有时你可以将 Strategy实现为可供各Context共享的无状态的对象来减少这一开销。任何其余的状态都由 Context维护。Context在每一次对Strategy对象的请求中都将这个状态传递过去。共享的 Strategy不应在各次调用之间维护状态。
7. iOS应用分析
例如,我们在验证用户输入的表单的时候,加入包括电话输入框的验证和邮件输入框的验证,这两部分的验证算法是不同的,如果把这个算法看成一个函数,他几乎有相同的输入参数和返回参数。我们可以把这个相同的函数可以抽象为基类(InputValidator)的一个方法(bool validateInput(input,error)),然后抽象出两个具体的策略类:电话验证类(PhoneValidator)和邮件验证类(EmailValidator),他们需要在各自的实现里面去复写父类的验证方法。为了能够正常的调用到验证类的验证方法,我们需要自定义一个UITextField的子类CustomTextField,其中有一个InputValidator类型的引用和一个validate方法,该方法里面调用InputValidator的验证方法,然后在textFieldDidEndEditing代理方法里面调用CustomTextField的validate方法,这样就不用我们在判断输入是否合法的时候通过if else去处理每种逻辑,而且这样做方便扩展,提高可复用性。
实例:排序算法,NSArray的sortedArrayUsingSelector;经典的鸭子会叫,会飞案例。
(六)工厂模式
工厂模式我的理解是:他就是为了创建对象的
创建对象的时候,我们一般是alloc一个对象,如果需要创建100个这样的对象,如果是在一个for循环中还好说,直接一句alloc就行了,但是事实并不那么如意,我们可能会在不同的地方去创建这个对象,那么我们可能需要写100句alloc 了,但是如果我们在创建对象的时候,需要在这些对象创建完之后,为它的一个属性添加一个固定的值,比方说都是某某学校的学生,那么可能有需要多些100行重复的代码了,那么,如果写一个-(void)createObj方法,把创建对象和学校属性写在这个方法里边,那么就是会省事很多,也就是说我们可以alloc 创建对象封装到一个方法里边,直接调用这个方法就可以了,这就是简单工厂方法
代码结构如下
Animal 类
@interface Animal :NSObject
@proterty(nonatomic,strong) NSString *name;
-(void)laugh;
@end
Dog类
@interface Dog:Animal
@end
Cat类
@interface Cat:Animal
@end
创建对象的工厂类
.h
@interface AnimalFactory:NSObject
+(Dog *)createDog;
+(Cat *)createCat;
@end
.m
@implementation AnimalFactory
+(Dog *)createDog{
Dog *dog=[[Dog alloc]init];
dog.name=@“baby”;
return dog;
}
+(Cat *) createCat{
Cat *cat=[[Cat alloc]init];
return cat;
}
Main.m文件
Dog *dog=[AnimalFactory createDog];
Cat *cat=[AnimalFactory createCat];
这是简单工厂模式
现在创建100个Dog对象,如果这100个对象写在程序中的不同地方,按上边的方法是需要把Dog *dog=[AnimalFactory createDog];这一句话写在程序中很多不同的地方,那么现在有一个需求,就是如果需要把这些创建的100个Dog对象全部变成Cat对象,那么按照刚才的那个做法,就需要在这100句代码中把createDog方法变成createCat方法了,这样做还是很复杂
那么这个时候用工厂方法模式就能解决这个难题了
工厂方法模式是为每一个要创建的对象所在的类都相应地创建一个工厂
代码如下
@interface AnimalFactory:NSObject
-(Animal*)createAnimal;
@end;
Dog工厂类
@interface DogFactory:AnimalFactory;
@implementation DogFactory
-(Animal *)createAnimal{
retrurn [[Dog alloc]init];
}
@end
Cat工厂类
@interface CatFactory:AnimalFactory;
@implementation Cat Factory
-(Animal *)createAnimal
retrurn [[Cat alloc]init];
}
@end
Main.m
AnimalFactory *dogFactory=[[DogFactory alloc]init];
Animal *animal1=[dogFactory createAnimal];
[animal1 laugh];
Animal *animal2=[dogFactory createAnimal];
[animal2 laugh];
…….
Animal *animal100=[dogFactory createAnimal];
[animal100 laugh];
这样话如果要把100个Dog改为Cat的话,只需要吧DogFactory改为CatFactory就可以了
但是工厂方法也有它的限制:
1.要创建的类必须拥有同一个父类
2.要创建的类在100个不同的地方所调用的方法必须一样