前言
在《设计模式:可复用面向对象软件的基础》一书中所介绍了23 种经典设计模式,不过设计模式并不仅仅只有这 23 种,本文将抽丝剥茧说一下我们iOS开发中常用到的几种设计模式。
- 单列模式
- 工厂模式
- 装饰模式
- 代理模式
- 观察者模式
- 命令模式
- 享元模式
单列模式
单例模式可以保证系统中一个类只有一个实例,类似全局变量,在系统任意位置都能访问单例对象。
使用场景
整个应用程序需要共享一份资源的时候,可以考虑使用单列
- [UIApplication sharedApplication];
- [NSNotificationCenter defaultCenter];
- [NSFileManager defaultManager];
- [NSUserDefaults standardUserDefaults];
- [NSURLCache sharedURLCache];
- [NSHTTPCookieStorage sharedHTTPCookieStorage];
实现方式
主要看下常用的创建的单列方式,不太常用的就不说了,免得误导
dispatch_once单例
+ (instancetype)shareInstance
{
static Singleton* single;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
single = [[Singleton alloc] init];
});
return single;
}
@synchronized单例
@implementation Singleton
+ (instancetype)shareInstance
{
static Singleton* single;
@synchronized(self){
if (!single) {
single = [[Singleton alloc] init];
}
}
return single;
}
@end
目前大部分使用的都是dispatch_once这种方式,可以解决同步多线程问题又不影响性能
工厂模式
定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到子类。
UML图
- 抽象工厂(IFactory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法 newProduct() 来创建产品。
- 具体工厂(actory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
- 抽象产品(IProduct):定义了产品的规范,描述了产品的主要特性和功能。
- 具体产品(Product):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。
使用场景
产品的实际创建在工厂类中,产品类的实现如何变化,调用者都不需要关心,只要关心产品的接口即可(说白了就是不用关系具体实现细节),比如
在 iOS 中,类簇的使用是比较普遍的,如 NSNumber,NSArray,NSString 等,属于工厂模式的一种应用,隐藏了具体的实现类,只暴露出简单的接口
+ (NSNumber *)numberWithInt:(int)value;
+ (NSNumber *)numberWithBool:(BOOL)value;
+ (NSNumber *)numberWithFloat:(float)value;
+ (NSNumber *)numberWithDouble:(double)value;
demo
查看demo代码
interface IProduct {
public void productMethod();
}
class Product implements IProduct {
public void productMethod() {
System.out.println("产品");
}
}
interface IFactory {
public IProduct createProduct();
}
class Factory implements IFactory {
public IProduct createProduct() {
return new Product();
}
}
public class Client {
public static void main(String[] args) {
IFactory factory = new Factory();
IProduct prodect = factory.createProduct();
prodect.productMethod();
}
}
装饰模式
不改变原有对象的前提下,动态地给一个对象增加一些额外的功能。
UML图
- Component:抽象组件,定义了一组抽象的接口,规定这个被装饰类有哪些功能。
- ConcreteComponent: 实现这个抽象组件的所有功能。
- Decorator:装饰器角色, 它持有一个Component 对象实例的引用。
- ConcreteDecorator:具体的装饰器实现者,负责实现装饰器角色定义的功能
使用场景
- 需要扩展一个类的功能,或给一个类添加附加职责。
- 需要动态的给一个对象添加功能,这些功能可以在动态的撤销。
- 需要增加由一些基本功能的排列组合而产生的非常大量的功能,从而使继承关系变得不现实。
- 当不能采用生成子类的方法进行扩充时
比如:
iOS中的Category是一种变相的装饰者模式,他的原理是在编译的时候动态为原来的类添加方法,而不是拥有一个原始类的实例,严格意义上不是装饰者,但是却和装饰者思想很像,在需要添加的功能很少的时候可以用Category
demo
查看demo代码
1、 Component
@protocol BattercakeProtocol <NSObject>
- (NSString *)descriptionString;
- (float)price;
@end
1、 ConcreteComponent
@interface Battercake : NSObject<BattercakeProtocol>
@end
@implementation Battercake
- (NSString *)descriptionString {
return @"煎饼";
}
- (float)price {
return 8;
}
@end
1、 Decorator
//装饰模式的核心在于抽象装饰类的设计,其典型的代码如下:
@interface AbstractDecorator : NSObject<BattercakeProtocol>
@property (nonatomic, strong) id<BattercakeProtocol>concreteComponent;
+ (instancetype)decoratorFor:(id<BattercakeProtocol>)concrete;
@end
@implementation AbstractDecorator
+ (instancetype)decoratorFor:(id<BattercakeProtocol>)concrete {
AbstractDecorator *decorator = [self new];
decorator.concreteComponent = concrete;
return decorator;
}
- (NSString *)descriptionString {
return [self.concreteComponent descriptionString];
}
- (float)price {
return [self.concreteComponent price];
}
@end
1、 ConcreteDecorator(具体装饰者)
@interface EggDecorator : AbstractDecorator
@end
@implementation EggDecorator
- (NSString *)descriptionString {
return [NSString stringWithFormat:@"%@ %@", [super descriptionString], @"加一个鸡蛋"];
}
- (float)price {
return [super price] + 1;
}
@end
1、 最终调用流程
id<BattercakeProtocol> battercake = [Battercake new];
battercake = [EggDecorator decoratorFor:battercake];
NSLog(@"%@, 价格:%@", [battercake descriptionString], @([battercake price]));
// 打印结果
煎饼 加一个鸡蛋 加一个鸡蛋 加一跟香肠, 价格:12
代理模式
代理模式即委托模式,为某个对象提供一个代理,并由这个代理对象控制对原对象的访问。
涉及及的角色:
- 协议:用来指定代理双方可以做什么,有哪些必须实现
- 委托:根据指定的协议,指定代理去完成什么功能
- 代理:根据指定的协议,完成委托方需要实现的功能
使用场景
当一个类的某些功能需要由别的类来实现,但是又不确定具体会是哪个类实现
- 自定义的delegate
# delegate定义方式
@protocol XXXDelegate<NSObject>
@optional
- 自定义delegate
- 系统提供的delegate。比如:UITableviewDelegate,UICollectionViewDelegate等
demo
比如房产中介(代理方)和卖家(委托方),卖家委托中介卖房,它们共同遵守的协议就是卖房
查看demo代码
// 协议
@protocol SellHouseDelegate<NSObject>
@optional
- (void) sellHouse;//卖房
@end
//委托方(卖家)
@interface Seller : UIViewController
@property (nonatomic,weak) id <SellHouseDelegate>delegate;
@end
// 在Seller.m文件中通知委托去给自己找顾客卖房
- (void)sellHouse{
if (self.delegate && [self.delegate respondsToSelector:@selector(sellHouse)]) {
[self.delegate sellHouse];
}
}
//代理(中介)
@implementation Agency<SellHouseDelegate>
- (void) sellHouse{
//由中介帮卖房找顾客卖房
}
@end
观察者模式
定义对象之间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。观察者模式又叫:发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式
UML图
- 发生改变的对象被称为观察目标(Subject)
- 被通知去响应变化的被称为观察者(Observer)
铃响,学生上课;铃再响,学生下课”。在这个场景中铃声就是同学们的观察目标,而学生就是观察者。
- Subjec: 目标,即被观察者。其中会定义一个观察者集合,用来存放任意数量的观察者,并提供管理这些观察者的方法。同时定义了通知的方法notify()。这个类可以是接口,抽象类或者具体类;
- ConcreteSubject: 具体的目标类。当其状态改变时,会向各个观察者发出通知,此类可以根据情况来决定是否对目标类进行扩展;
- Observer: 观察者类对观察目标的改变做出响应。一般定义为接口,该接口声明了更新数据的方法update();
- ConcreteObserver: 具体观察者,它是真正响应变化的类,在一般情况下它维护一个指向具体目标对象的引用,它具体存储观察者的有关状态(例如下课的铃响,学生记住下课的状态),这些状态要和具体目标的状态保持一致。它实现了在Observer接口中的update()方法。可以调用观察目标对象的addObserver()和removeObserver()将自己添加到观察者队列或者从观察者队列中删除。
- ConcreteObserver: 具体观察者
使用场景
当一个对象发生改变的时候,需要将它的变化进行反馈。iOS中主要的使用场景是下面两种情况,这里不做过多阐述了
- Notification
- KVO
命令模式
将一个请求封装为一个对象,从而让我们可用不同的请求对客户进行参数化,或者说如何将“行为请求者”与“行为实现者”解耦?将一组行为抽象为对象,实现二者之间的松耦合。这就是命令模式
UML图
- Command类:是一个抽象类,类中对需要执行的命令进行声明,一般来说要对外公布一个 execute 方法用来执行命令。
- ConcreteCommand类:Command类的实现类,对抽象类中声明的方法进行实现。
- Invoker类:调用者,负责调用命令。
- Receiver类:接收者,负责接收命令并且执行命令。
使用场景
- NSinvocation类的设计就用到了命令模式。一个Invocation对象包含了目标对象、方法选择器、方法参数
- Cocoa中的target-action机制也是命令模式的一种实现
demo
查看demo代码
1、 定义接口CommandProtocol
// 定义抽象类接口
@protocol CommandProtocol <NSObject>
@required
//命令的执行
- (void)excute;
@end
1、 定义ConcrateCommand遵守协议,持有接收者
@interface ConcrateCommand : NSObject<CommandProtocol>
- (instancetype)initWithReciever:(Reciever *)reciever;
@end
@interface ConcrateCommand()
@property(nonatomic, strong) Reciever* reciever;
@end
@implementation ConcrateCommand
- (instancetype)initWithReciever:(Reciever *)reciever
{
self = [super init];
if (self) {
self.reciever = reciever;
}
return self;
}
-(void)execute{
[self.reciever doSomething];
}
@end
1、 接收者Reciver实现具体功能
@interface Reciever : NSObject
//具体方法
-(void)doSomething;
@end
@implementation Reciever
-(void)doSomething{
NSLog(@"接收者实现具体方法的功能");
}
@end
1、 Invoker 请求者,持有ConcrateCommand的引用,执行ConcrateCommand遵守协议的方法;
@interface Invoker()
@property (nonatomic,strong)Reciever *reciever;
@property(nonatomic, strong)id <CommandProtocol> command;
@end
@implementation Invoker
- (instancetype)initWithCommand:(ConcrateCommand *)command
{
self = [super init];
if (self) {
self.command = command;
self.reciever = reciver;
}
return self;
}
-(void)doSomething{
[self.command execute];
}
1、 Client 最后调用
//创建接收者
Reciever *reciver = [[Reciever alloc]init];
//创建命令(分离)->解藕和
ConcrateCommand *command = [[ConcrateCommand alloc]initWithReciever:reciver];
//创建请求者
Invoker *invoker = [[Invoker alloc]initWitCommand:command];
[invoker doSomething];
享元模式
享元模式主要用于减少同一类对象的大量创建,以减少内存占用,提高项目流畅度。在享元模式中有两个重要的概念,即内部状态和外部状态:
- 内部状态:在享元对象内部不随外界环境改变而改变的共享部分
- 外部状态:随着环境的改变而改变,不能够共享的状态就是外部状态
由于享元模式区分了内部状态和外部状态,所以我们可以通过设置不同的外部状态使得相同的对象可以具备一些不同的特性
UML图
- 抽象享元角色(Flyweight):享元对象抽象基类或者接口,同时定义出对象的外部状态和内部状态的接口或实现;
- 具体享元角色(ConcreteFlyweight):实现抽象角色定义的业务。该角色的内部状态处理应该与环境无关,不能出现会有一个操作 改变内部状态,同时修改了外部状态;
- 享元工厂(FlyweightFactory):负责管理享元对象池和创建享元对象;
使用场景
在程序设计中,如果发现需要大量细粒度的类对象来表示数据,而且这些类除了几个参数不同以外,其他的属性都是相同的,这时候就可以使用享元模式
比如iOS中的UITableViewCell,UICollectionViewCell就使用了享元模式
![]()
demo
查看demo代码
1、 先创建一个Cell类,里面有内部状态和设置外部状态的方法,表示抽象享元类;
@interface Cell : NSObject
@property(nonatomic, copy, readonly) NSString *cellID; //内部状态
- (void)setRowIndex:(NSInteger)rowIndex; //外部状态
@end
@implementation Cell
- (NSString *)cellID {
return @"cellID";
}
- (void)setRowIndex:(NSInteger)rowIndex {
NSLog(@"Cell复用ID = %@, rowIndex = %ld", self.cellID, rowIndex);
}
@end
1、 再创建ImageCell和TextCell两个类,都继承自Cell类,表示具体享元类
// ImageCell
@interface ImageCell : Cell
@end
@implementation ImageCell
- (NSString *)cellID {
return @"ImageCell";
}
@end
// TextCell
@interface TextCell : Cell
@end
@implementation TextCell
- (NSString *)cellID {
return @"TextCell";
}
@end
1、 最后创建一个CellFactory类,里面有一个享元池,表示享元工厂类。
@interface CellFactory : NSObject
+ (instancetype)sharedInstance;
- (Cell *)getCellWithCellID:(NSString *)cellID;
@end
// .m文件
@interface CellFactory ()
@property(nonatomic, strong) NSMutableDictionary *dict; //享元池
@end
@implementation CellFactory
- (instancetype)init
{
self = [super init];
if (self) {
_dict = [NSMutableDictionary dictionary];
}
return self;
}
+ (instancetype)sharedInstance { //单例模式
static CellFactory *factory;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
factory = [CellFactory new];
});
return factory;
}
- (Cell *)getCellWithCellID:(NSString *)cellID { //享元工厂类核心代码
if ([self.dict.allKeys containsObject:cellID]) {
return [self.dict objectForKey:cellID];
} else {
if ([cellID isEqualToString:@"ImageCell"]) {
ImageCell *imageCell = [ImageCell new];
[self.dict setObject:imageCell forKey:cellID];
return imageCell;
} else if ([cellID isEqualToString:@"TextCell"]) {
TextCell *textCell = [TextCell new];
[self.dict setObject:textCell forKey:cellID];
return textCell;
} else {
return nil;
}
}
}
@end
1、 使用
CellFactory *factory = [CellFactory sharedInstance];
Cell *imageCell1 = [factory getCellWithCellID:@"ImageCell"];
Cell *imageCell2 = [factory getCellWithCellID:@"ImageCell"];
NSLog(@"imageCell1 = %p", imageCell1);
NSLog(@"imageCell2 = %p", imageCell2);
NSLog(@"-------------------");
Cell *textCell1 = [factory getCellWithCellID:@"TextCell"];
Cell *textCell2 = [factory getCellWithCellID:@"TextCell"];
NSLog(@"textCell1 = %p", textCell1);
NSLog(@"textCell2 = %p", textCell2);
// 打印结果
imageCell1 = 0x600002dbcc70
imageCell2 = 0x600002dbcc70
-------------------
textCell1 = 0x600002dac660
textCell2 = 0x600002dac660
总结:从享元工厂类得到的对象内存地址一样,很好地复用了对象。
此部分参考:iOS 设计模式之十二(享元模式)