OC 设计模式全解析(iOS平台)
设计模式理解
设计模式一般会问你在项目中常用的设计模式有那些?等你说完之后会问你怎么用的,什么场景用的?它的优缺点是什么?遵循了什么原则?违背了什么原则?...
UML类图

依赖是弱关联:只是使用到了
关联:需要引用,表示拥有关系,需要添加属性
聚合关系:也是一种关联关系,跟关联代码表现是相同的,只不过在语意上有区别,关联关系的对象间是相互独立的,而聚合关系的对象之间存在包容关系。
组合关系:也是一种关联关系,耦合度比聚合关系更强,表现的是整体-部分的关系,而且整体负责部分的生命周期,部分是不暴露给外部,而且部分不能脱离整体单独使用。
聚合(空心菱形)和组合(实心菱形)的区别
聚合关系中,被聚合的类的构造函数必须带聚合类形参,聚合类可以独立于被聚合类而存在,例如一只鸟可以独立于鸟群而生存;组合关系中,被组合类的构造函数中必须先一步构造组合类,后者是前者构造出来的必要条件,是其一部分,不能独立存在,例如翅膀不能独立于鸟而生存。
设计原则
1、单一指责原则:一个类只需要有一个职责,如果包含了多个职责,需要拆分成多个类来管理。
2、开闭原则:对修改封闭,对扩展开放。也就是尽量不要修改类的内部代码,可以使用分类来代替,或者使用继承。
3、接口隔离原则:不同的功能需要定义成不同的接口,而不要把所有的功能都放在一个接口中,防止接口冗余。
4、里氏替换原则:所有使用父类的地方都可以使用子类来替换,所以就是尽量少重写父类的方法,子类可以增加方法。
5、依赖倒置原则:使用者应该尽量不依赖实现者,他们都应该依赖实现者的抽象。
6、迪米特法则:一个对象应该对其他对象保持最少的了解,实现低耦合,高内聚。
7、组合/聚合模式:少用继承,多用组合关系来实现(比如一个人的职业,person组合了职业类而不是职继承了人)

13种常用的设计模式:
1、适配器模式
2、策略模式
3、观察者模式
4、原型模式/外观模式
5、装饰模式
6、工厂模式
7、桥接模式
8、代理模式
9、单例模式
10、备忘录模式
11、建造者模式(生成器模式)
12、命令模式
13、组合模式
一、创建型模式:主要用于创建对象
1.在软件工程中,创建型模式是处理对象创建的设计模式,试图根据实际情况使用合适的方式创建对象。基本的对象创建方式可能会导致设计上的问题,或增加设计的复杂度。创建型模式通过以某种方式控制对象的创建来解决问题。
2.创建型模式由两个主导思想构成。一是将系统使用的具体类封装起来,二是隐藏这些具体类的实例创建和结合的方式。
3.创建型模式旨在将系统与它的对象创建、结合、表示的方式分离。这些设计模式在对象创建的类型、主体、方式、时间等方面提高了系统的灵活性
***工厂方法:提供简单方便的创建对象的方法,定义一个创建对象的接口,由其子类去实例化要创建的类
1、简单工厂:工厂类、抽象产品类、产品1、产品2 creatWithType来创建产品
2、工厂方法类:抽象工厂creat、工厂1、工厂2、抽象产品、产品1、产品2 fac1 creat、fac2 creat
3、抽象工厂:抽象工厂 creatA creatB 工厂1、工厂2、抽象产品A、产品A1、产品A2、抽象产品B、产品B1、产品B2
实际使用场景:微信支付、支付宝支付
工厂方法和抽象工厂区别:
1、工厂方法只有一个抽象产品,抽象工厂有多个抽象产品
2、工厂方法的具体工厂只能创建一个具体产品类实例,抽象工厂的具体工厂可以创建多个具体产品实例
3、工厂方法是由工厂子类自行决定实例化哪个类,抽象工厂是自己决定实例化哪个类。
***建造者模式:
也是提供简单创建产品的方法,不过他是用来创建复杂的产品。把产品的创建过程(在Director中)和产品的表现(在builder中)分离。相当于工厂模式的扩展。工厂模式是把创建产品的逻辑放在工厂中,而建造者模式是增加了一个director,建造者类相当于工厂,生产产品组件,director内部将建造者创建产品的过程进行组合,并返回产品。
***原型模式:
应用于“复制”操作的模式,就是原型模式,也就是我们所说的copy,不过这里的复制是深复制,是复制原对象,并开辟新的内存区域。从功能角度来看,只要复制自身比重新实例化都要好,就应该使用原型模式。
OC中通过NSCopying协议,以及实现copyWithZone方法,实现对象的复制
举例:
-(void)viewDidLoad {
[super viewDidLoad];
YZPerson *p1 =[[YZPerson alloc]init];
p1.name = @"jack";
p1.age = 10;
YZPerson * p2 =[p1 copy];
p2.name = @"rose";
}
@interface YZPerson : NSObject <NSCopying>
@end
#import "YZPerson.h"
@implementation YZPerson
-(id)copyWithZone:(nullable NSZone *)zone{
YZPerson *p =[[YZPerson allocWithZone:zone]init];
p.name = self.name;
p.age = self.age;
return p;
}
@end
现实使用实例:比如我传一个对象到一个控制器,如果直接传对象过去,然后另一个控制器中修改了对象的内容,则另一个控制器的内容也会因为对象的改变而改变,所以,需要通过copy重新复制一个对象进行传递
***单例模式:
保证在整个程序运行中,只会初始化一次,只会存在一个内存。并且提供该实例的全局访问方式
手写单例
Static Singleton *instance = nil;
+(instancetype)sharedSingleton {
if(instance == nil){
instance =[[self alloc]init];
}
}
+(instancetype)copyWithZone:(struct _NSZone *)zone {
static dispatch_once_t onceToken;
dispatch_once(&onceToken,^{
instance =[super allocWithZone:zone];
});
return instance;
}
-(id)copy {
return self;
}
-(id)mutableCopy {
return self;
}
二、结构型模式:用于处理类或对象的组合
***适配器模式:
将一个类的接口转换成客户端希望的接口,适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作,分为类适配器,和对象适配器
类适配器:
使用的继承适配者

对象适配器:
对适配者只是依赖

使用场景:
1、delegate就是使用的适配者模式,比如UITableView就是相当于使用者(客户端),他的delegate对象就是适配者,需要实现UITableview的协议,被适配者就是UI控件,需要又代理对象对UI控件进行组合成UITableviewCell进行返回
2、对一些第三方库的封装也是使用的适配者模式,比如对网络请求的简单封装,客户端想要发送一个post请求,首先定义协议类提供post方法,然后创建网络管理的adapter,netAdapter,实现post方法调用第三方库(adaptee被适配者)的post相关方法
***桥接模式:
将抽象与实现部分分离,使他们都可以独立的变化
使用场景:抽象和实现都可以通过子类独立进行扩展
1、不想在抽象与其实现之间形成固定的绑定关系,这样就能在运行时切换实现。
2、抽象及其实现都可以通过子类化独立进行扩展
3、对抽象的实现进行修改不影响客户端代码
4、如果每个实现需要额外的子类以细化抽象,则说明有必要把他们分成两部分
5、想在带有不同抽象接口的多个对象之间共享实现
组成部分:
1、抽象接口的抽象类
2、抽象接口的子类
3、抽象实现类
4、抽象实现类的子类

具体应用





具体例子:jsBridge,实现原生与h5进行交互,是双向桥
***组合模式:
这种模式将互相关联的对象合成为树形结构,以表现部分-全部的层次结构。而且让客户端可以统一的单独处理部分与整体对象。
组成部分:
抽象接口:提供公用的方法和属性
实现类:使用一个数组聚合抽象接口对象
UML图

使用,可以使用组合模式创建树形结构,以及遍历该结构 文件系统的创建和遍历
示例代码:
1、使用OC实现二叉树(2才是组合模式的示例)



2、组合模式



***装饰者模式:
是一种动态扩展对象功能的模式,需要对被装饰者进行引用实例化对象。或者内部包含被装饰者的方法,然后在该方法中调用被装饰者的方法,并且可以添加一些自己的逻辑。
当然也可以通过分类来实现
标准的装饰者模式UML图

比如我们要装饰一个image,首先需要引用image,并且包含image属性,添加一个move方法,调用时候传入image,并调用装饰者的move方法。
***外观模式:
为子系统中一组不同的接口提供统一的接口。外观定义了上层接口,通过降低复杂度和隐藏子系统间的通信及依存关系,让子系统更易于使用。
其实也就是一种封装方式。

***享元模式:
分享元素,分享内存,以达到复用的目的。
享元模式是有一个享元池,享元池中每一组享元对象都有一个标识符key。当需要元素的时候,先去享元池中通过标识符key进行查找,如果存在的话就直接拿来用,如果不存在的话就直接创建,当元素不用的时候,就把元素放回享元池中,等待复用,这样的话就避免每次使用都创建一份元素,节省内存。
***代理模式:
对象想要做一件事情,但是他不自己去做,而是设置其他对象为自己的代理,然后让其他对象去做。
首先对象需要创建代理协议,也就是提供一些需要代理对象去做的api接口,然后引用一个代理对象为成员变量,当对象想要实现某些功能的时候,调用代理对象的相应的协议方法。然后代理对象实现协议方法就可以
4、行为型模式:用于描述类和对象如何交互和如何分配职责
***责任链模式(职责链模式):
通俗来讲就是一层一层的处理事件。比如有一个事件需要处理,然后有几个对象都能处理此事件,我们需要把这几个对象通过引用成员变量的方式串起来,然后把事件传给这一系列对象进行处理。
应用场景:有多个对象都可以处理请求,而处理程序只有在运行时才能确定,而且不需要关心是谁处理的,也不用关心处理过程。
角色:handle处理抽象类、concretHandle具体处理者

Handle是抽象类:包含Handle类型的成员变量指向下一个nextHandle,以及一个handleRequest方法([nextHandle handleRequest]),具体实现类重写handleRequest方法(如果能自己处理就写处理事件,否则,调用[super handleRequest])
游戏中的责任链模式应用实例

应用实例:iOS的响应者链条。需要知道原理和过程。
***命令模式:
命令模式将命令封装为对象,调用者通过调用命令来实现对目标对象的操作,不必要了解目标的任何细节。因为命令被封装成了对象,所以可以把命令当作参数进行存储,所以这样就提供了撤销和恢复操作。
结构:
Client:创建命令对象,并设置接收器
Invoke:命令管理者以及调用者
Commad:命令的抽象类或者抽象接口
ConcreteCommand:命令的实现类
Receiver:命令的接受者,也是目标者

Invok:excute、rollBackExcute执行和撤销方法,用来执行命令(其实是调用命令的执行,其实是调用命令的receiver的执行),存储命令和删除命令、撤销命令(其实是调用的命令的撤销,其实是执行的命令的receiver的撤销)
Commad:命令用来执行和撤销,其实是调用的receiver的执行和撤销,需要设置自身的receiver
Receiver:命令的接收者,也是目标者,实际用来执行和撤销的对象
***解释器模式:
给定一个语言,定义该语言的文法表示,并定义一个解释器,这个解释器使用该表示来解释语言中的语句。
优点:可以很方便的扩展文法规则。
缺点:文法匹配需要大量的循环和递归,执行效率比较低。会引起类膨胀,因为一个文法至少要定义一个类
***迭代器模式:
本质上就是遍历聚合或者组合对象。
使用场景:需要访问聚合(组合)对象的内容而不暴露其内部表示。需要使用多种方式遍历组合对象。需要提供一个统一的接口,用来遍历各种类型的组合对象。
分为内部迭代器和外部迭代器。

NSEnumerator外部迭代器

内部迭代器

***中介者模式:
定义一个中介者来封装一系列对象之间的交互,使原有对象之间的耦合松散,而且还可以动态改变他们之间的交互。中介者模式又叫调停模式,是迪米特法则的典型应用。一个对象应该对其他对象保持最少的了解,实现低耦合,高内聚。
实际应用:MVC模式就是中介者模式
***备忘录模式:
在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可以将该对象恢复到原先保存的状态。
备忘录模式的组成部分:
1、原发器:originator:他是一个普通类,可以创建并返回一个存储他内部状态的备忘录,也可以使用备忘录来恢复其内部状态,一般将需要保存内部状态的类设计为原发器。
2、备忘录:memento:存储原发器的内部状态,根据原发器来决定保存哪些内部状态。
3、caretaker:管理者:负责保存备忘录,但是不会对备忘录的内容进行操作或检查。
结构图:

实际应用:归档解档。
需要归档解档的类就是originator原发器
caretaker(管理者):NSKeyedArchiver archive和unarchive。
备忘录(Memento)需要遵循NSCoding协议,使用的是类NSCoder
-(void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:self.name forKey:@"name"];
[encoder encodeInt:self.age forKey:@"age"];
[encoder encodeFloat:self.height forKey:@"height"];
}
-(id)initWithCoder:(NSCoder *)decoder {
self.name =[decoder decodeObjectForKey:@"name"];
self.age =[decoder decodeIntForKey:@"age"];
self.height =[decoder decodeFloatForKey:@"height"];
return self;
}
//归档到Document文件夹
[NSKeyedArchiver archiveRootObject:person toFile:path];
//解档类
Person *person =[NSKeyedUnarchiver unarchiveObjectWithFile:path];
// 可以利用归档实现深复制,临时存储对象的数据
NSData *data =[NSKeyedArchiver archivedDataWithRootObject:person1];
// 解析data生成一个新的person类
Student *person2 =[NSKeyedUnarchiver unarchiveObjectWithData:data];
***观察者模式:
观察者模式定义了一种一对多的组合关系,以便一个对象的状态发生变化时,所有依赖于他的对象都得到通知并自动刷新。

应用实例:KVO和通知
***状态模式:
允许一个对象在其内部状态改变时修改他的行为,对象看起来似乎修改了他的类。
该模式用于解决系统中复杂对象的状态转化以及不同状态下行为的封装问题。
组成部分:
1、Contex(环境类):环境类又称为上下文,他是拥有多种状态的对象。
2、Status(抽象状态类):用于定义状态处理方法
3、ConcreteStatus(具体状态类):用于实现状态处理方法
结构图

具体状态类可通过Context(环境类)统一实现状态的转换

***策略模式:
定义一系列算法,把他们一个个封装起来,并且使他们可以互相替换。
角色
环境类:使用不同策略的类
策略抽象类:定义策略抽象方法
策略具体类:策略抽象方法实现
结构图:

结构图跟状态模式的很像。
策略模式: 封装的是一系列的算法,用户可以自行选择。比如出行有地铁出行,公交出行,我们可以使用调用出行类的[Go goWIth:subWay];
状态模式: 封装的是内部的运行状态,但是对外而言都是看不见的 [Login login]; login里面自己通过逻辑判断然后设置status是哪个类。
***模版方法模式:
就是定义一个父类,来定义接口规范,然后不同的行为在子类中实现。
简单来说就是继承、重写、和代码复用
***访问者模式:
提供一个作用于某对象结构中的各元素的操作表示,他使我们可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
角色:
Visit(抽象访问者):为元素对象结构中每一个具体元素声明一个访问操作,这个操作的名称或者参数类型可以清楚知道需要访问的具体元素的类型,具体访问者需要实现这些操作方法,定义这些元素的访问操作。
这些访问方法的命名一般有两种方式:一种是直接在方法名中标明待访问元素对象的具体类型,如visitElementA(ElementA elementA),还有一种是统一取名为visit(),通过参数类型的不同来定义一系列重载的visit()方法。
ConcreteVisitor(具体访问者):实现了抽象类的用于访问对象结构中每一个元素的操作。
Element(抽象元素):定义一个accept()方法,该方法通常以抽象访问者为参数。
ConcreteElement(具体元素):具体实现了accept()方法,在方法中调用访问者的访问该具体元素的方法,完成对该元素的操作。
ObjectStructure(对象结构):对象结构是一个元素的集合,用于存放元素对象,并且提供了遍历其内部元素的方法,他可以结合组合模式来实现,也可以是一个简单的集合对象,比如一个List对象。
结构图:

代码示例:






优点:增加新的访问者很简单,可以对元素进行不同的操作。
缺点:增加新的元素比较麻烦,需要访问者定义访问该元素的方法,并且每个具体访问者都需要实现该方法。