《Objective-C编程之道:iOS设计模式解析》

对象创建

对象创建主要涉及到5个设计模式,分别为:原型,工厂方法,抽象工厂,生成器,单例。

原型(Prototype)模式

使用原型实例指定创建对象的种类,并通过复制这个原型创建新的对象—《设计模式》(Addison-Wesley)

初看原型感觉很陌生,但说到拷贝复制可能就比较熟悉了。原型指用于复制的对象,就像克隆人是以人为原型通过克隆创建了克隆人。

优点: 使用原型创建对象更方便且效率更高。就像印刷术,写一个字需要有很多的比划,如果通过印章则能快速的构建字。

CocoaTouch: 集合对象比如数组,字典等都支持原型模式,不过默认为浅拷贝。

深复制与浅复制

深复制不仅复制原型指向的资源地址,还会复制原型的资源,也就是会在内存中开辟一块用于存储原型的资源。浅复制则只复制原型指向的资源地址。

其中iOS 集合的深复制与浅复制指出

在非集合类对象中:对immutable对象进行copy操作,是指针复制,mutableCopy操作时内容复制;对mutable对象进行copy和mutableCopy都是内容复制。用代码简单表示如下:

  • [immutableObject copy] // 浅复制
  • [immutableObject mutableCopy] //深复制
  • [mutableObject copy] //深复制
  • [mutableObject mutableCopy] //深复制

深复制协议

Cocoa中给NSObject提供了NSCopying协议,让对象支持拷贝操作。通过实现NSCopying协议中的copyWithZone方法实现拷贝功能。

1
2
3
4
5
6
7
8
- (id)copyWithZone:(NSZone *)zone {
Person *person = [[[self class] allocWithZone:zone] init];
person.name = self.name;
person.nick = self.nick;
person._age = _age;
return person;
}

工厂方法

定义创建对象的接口,让子类决定实例化哪一个类。工厂方法使得一个类的实例化延迟到其子类。—《设计模式》(Addison-Wesley)

特点: 一个工厂对应一个产品,通过工厂生产产品(创建对象)。灵活,相比于抽象工厂更符合开放封闭原则。但会产生过多的类,一个产品对应一个工厂,在OC中相当于每一个产品的创建会多出2个文件。

核心代码

一个产品(PaperCanvasView)对应一个工厂(papaerCanvasViewGenerator)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// CanvasViewGenerator.h
- (CanvasView *)canvasViewWithFrame:(CGRect)aFrame;
// CanvasViewGenerator.m
- (CanvasView *)canvasViewWithFrame:(CGRect)aFrame {
return [[CanvasView alloc] initWithFrame: aFrame];
}
// PaperCanvasViewGenerator.m
- (CanvasView *)canvasViewWithFrame:(CGRect)aFrame {
return [[PaperCanvaseView alloc] initWithFrame: aFrame];
}
// 使用
- (void)loadCanvasViewWithGenerator:(CanvasViewGenerator *)generator {
CanvasView *canvasView = [generator canvasViewWithFrame: aFrame];
// ....
}

抽象工厂

提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。—《设计模式》(Addison-Wesley)

相比于工厂方法,抽象工厂不单单对应一个产品,可以通过组合的方式对应多个产品。例如小黄堡套餐,套餐里有一个小黄堡,一份薯条,一杯小可。由这三个产品组合形成小黄堡套餐。假设还有豪华大黄堡套餐,套餐里包括一个大黄堡,一份大薯,一杯大可。通过组合就可以形成套餐,但抽象工厂里如果要在套餐里添加一个产品就相对复杂。比如在套餐中添加一份鸡翅,则需在这个系列的所有套餐中都需要添加一份鸡翅。也就是需要修改多个类,从而添加一个产品,违反了开闭原则。

适用场景: 适用于组合结构不易变更的情况下,需切换系列产品的时候。比如网易云音乐有主题换肤这一功能,换肤包括导航栏颜色,图标颜色等。通过抽象工厂则很方便的切换主题颜色。

核心代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// BrandingFactory.h
+ (BrandingFactory *)factory;
- (UIView *)brandedView;
- (UIButton *)brandedbutton;
// BrandingFactory.m
+ (BrandingFactory *)factory {
if ([[self class] isSubClass: [AcmeBrandingFactory class]]) {
return [[AcmeBrandingFactory alloc] init];
} else if () {
}
}
// AcmeBrandingFactory.m
- (UIView *)brandedView {
return [[AcmeView alloc] init];
}
- (UIButton *)brandedbutton {
return [[AcmeButton alloc] init];
}
// 使用
BrandingFactory *factory = [BrandingFactory factory];
UIView *view = [factory brandedView];
UIButton *button = [factory brandedButton];

生成器

将一个复杂对象的构建与它的表现分离,使得同样的构建过程可以创建不同的表现。—《设计模式》(Addison-Wesley)

将复杂对象的构建分解为客户-指导者-生成器,使得对象的创建更易于管理与复用。

核心代码,其中CharacterBuilder是生成器,ChasingGame是指导者,通过指导者的方法创建不同的复杂对象,其中方法是通过生成器构建对象。

1
2
3
4
5
CharacterBuilder *characterBuilder = [[StandarCharacterBuilder alloc] init];
ChasingGame *game = [[ChasingGame alloc] init];
Character *player = [chasingGame createPlayer:characterBuilder];
Character *enemy = [chasingGame createEnemy:characterBuilder];

其中生成器中有客户的实例,CharacterBuilder生成器中提供方法对客户进行特征的设置逻辑,StandarCharacterBuilder继承CharacterBuilder则根据相关特征的角色定制具体的特征逻辑。ChasingGame则通过生成器CharacterBuilder构建具体角色,为具体角色做特征的配置。

单例

保证一个类仅有一个实例,并提供一个访问它的全局访问点。—《设计模式》(Addison-Wesley)

这个模式相对常用,比如UIApplication通过sharedApplication获取的实例,应用中有且只有一个UIApplication实例。

单例声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@implementation Singleton
static id sharedSingleton = nil;
+ (id)allocWithZone:(struct _NSZone *)zone {
if (!sharedSingleton) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedSingleton = [super allocWithZone:zone];
});
}
return sharedSingleton;
}
- (id)init {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedSingleton = [super init];
});
return sharedSingleton;
}
+ (instancetype)sharedInstance {
return [[self alloc] init];
}
+ (id)copyWithZone:(struct _NSZone *)zone {
return sharedSingleton;
}
+ (id)mutableCopyWithZone:(struct _NSZone *)zone {
return sharedSingleton;
}
@end

需要注意的是,如果单例是在多线程环境下访问,则需考虑线程安全的问题,因而需在判断sharedSingleton是否为nil时,添加互斥锁,以防有两个线程同时访问该实例,且该实例都为nil,则可能会造成两个线程都去创建。

接口适应

适配器

将一个类的接口转换成客户希望的另一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。—《设计模式》(Addison-Wesley)

OC实现适配器分为两种: 类适配器与对象适配器。该模式中由3个角色组成:Target目标接口,Adaptee被适配者与Adapter适配器。将被适配者转换为客户希望的目标接口。适配器模式就是使得原本由于目标接口与被适配者接口不兼容而不能一起工作的类可以一起工作。

适用场景

让视图更加强大,可以适配不同的数据,降低耦合性。在遗留代码复用,类库迁移方面使用。

对象适配器

对象适配器则是通过组合对被适配者的引用,遵循目标接口协议,实现协议中的方法通过调用被适配者引用调用相应的方法。

类适配器

通过适配器继承被适配者,适配器通过重载目标接口,调用超类的相应方法。该方法采用的是多继承的方式,但OC中没有多重继承,目标接口只有为协议时才能用适配器实现。所以一般不推荐使用。

范例

Cocoa Touch中的Delegate模式主要是适配器模式, 其中委托协议是Target,实现该协议的类是Adapter,例如VC中实现协议, 而被适配者则是应用程序中的其他类。

其中田伟宇在这篇文章iOS应用架构谈 网络层设计方案中就是通过应用该模式交付数据给业务层:

桥接

—《设计模式》(Addison-Wesley)

外观

—《设计模式》(Addison-Wesley)

对象去耦合

—《设计模式》(Addison-Wesley)

待补

抽象集合

待补

行为扩展

待补

算法封装

待补

性能与对象访问

待补

对象状态

待补

参考资料

iOS 集合的深复制与浅复制

iOS 中的 21 种设计模式