《创建型设计模式》之iOS系统框架实践

为了API的易用性、易维护性和健壮性,苹果工程师在iOS系统框架中其实运用了不少经典设计模式,而这些实践也正是因为良好的封装性,开发中我们虽日日相对,却也难以察觉它的存在。相对于其他生搬硬造隔靴搔痒的例子,这些我们熟悉的不能再熟悉的API方是学习设计模式的最佳案例。因此本系列拟以iOS系统框架中相关设计模式的实践为起点,探讨研究23种经典设计模式。

本文先讲述《创建型设计模式》(Creational Patterns)。

创建型设计模式是在创建一个类时用到的设计模式,总共有5种,其中工厂方法模式还可以根据实现的不同,分出简单工厂模式和工厂方法模式。

简单工厂模式(Factory Method)

iOS系统Foundation框架中的NSNumber所应用的就是简单工厂模式。

简单工厂模式主要解决不同情况下,需要创建不同子类,而这些子类又需要转化为公共父类让外界去使用的问题,因为这样对外接口只有一个,实际行为却因子类的具体实现而不同。拿NSNumber来说,传入IntFloatDoubleCharUnsignedChar等具体numberNSNumber返回的是对应的NSNumber子类,而我们使用时只知NSNumber,不知具体的子类。

1
2
3
4
5
6
7
8
9
import UIKit
let boolValue: Bool = true
let doubleValue: Double = 1.0

let boolN = NSNumber(value: boolValue)
let doubleN = NSNumber(value: doubleValue)

print(type(of:boolN))
print(type(of:doubleN))

输出结果为

1
2
__NSCFBoolean
__NSCFNumber

如果用简单工厂方法实现NSNumber(为了不与系统的NSNumber混淆,本文自己定义的NSNumber均去掉NS前缀,改为Number),代码大致如下:

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// 抽象产品
protocol Number {
func doubleValue() -> Double
func boolValue() -> Bool
}

// 生产工厂
class NumberFactory {
func createNumber(value: Bool) -> Number {
return __NSCFBoolean(value: value)
}
func createNumber(value: Double) -> Number {
return __CFNumber(value: value)
}
}

// 具体的产品A
private class __CFBoolean: Number {
let bool: Bool
init(value: Bool) {
bool = value
}

func doubleValue() -> Double {
return bool ? 1 : 0
}

func boolValue() -> Bool {
return bool
}
}

// 具体的产品B
private class __CFNumber: Number {
let double: Double
init(value: Double) {
double = value
}

func doubleValue() -> Double {
return double
}

func boolValue() -> Bool {
return double != 0
}
}

其中Number是抽象协议,负责定义行为,而__CFNumber__CFBoolean是实现了Number抽象协议的私有实体类,NumberFactory则是一个创建Number的工厂。

具体使用时,先创建工厂,然后根据需要创建具体的实体类:

1
2
3
4
5
// 先创建工厂
let factory = NumberFactory()
// 然后根据需要创建实体类
let boolN = factory.createNumber(value: false)
let doubleN = factory.createNumber(value: 2.0)

而由于Objective-C的初始化方法中可以直接返回子类型,因此不必创建一个单独的工厂类NumberFactory,直接将相应的工厂方法逻辑封装在NSNumberinit方法中即可:

1
2
3
4
5
6
7
8
@implementation NSNumber
- (NSNumber *)initWithBool:(BOOL)value {
return [[__NSCFBoolean alloc] initWithBool:value];
}
- (NSNumber *)initWithDouble:(double)value {
return [[__NSCFNumber alloc] initWithDouble:value];
}
@end

而在Swift中不可能从init初始化方法中返回一个子类。(Swiftinit方法除了return nil外不能有返回值)

工厂方法模式(Factory Method)

简单工厂模式中,工厂只有一个实体类NumberFactory,每当添加新的产品(即新实现Number协议的子类),都需要去修改这个工厂。
比如上文新添加一个针对Float实现Number协议的__CFFloat(系统中的NSNumber并没有实体子类__NSCFFloat,而是所有的数字类型都封装为__NSCFNumber),

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 新添加的具体的产品C
private class __CFFloat: Number {
let float: Float
init(value: Float) {
float = value
}

func doubleValue() -> Double {
return Double(float)
}

func boolValue() -> Bool {
return float != 0
}
}

那么NumberFactory也需要改动:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 生产工厂
class NumberFactory {
func createNumber(value: Bool) -> Number {
return __NSCFBoolean(value: value)
}
func createNumber(value: Double) -> Number {
return __CFNumber(value: value)
}
// 新添加的工厂方法
func createNumber(value: Float) -> Number {
return __CFFloat(value: value)
}
}

为解决这个弊端,可以将工厂NumberFactory也抽象一层,定义为一个协议:

1
2
3
4
// 抽象工厂
protocol NumberFactory {
func createNumber(value: Any) -> Number
}

然后针对不同的Number实体子类,都定义相应的工厂NumberFactory子类即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Bool 专用的工厂类
class BoolNumberFactory: NumberFactory {
func createNumber(value: Any) -> Number {
guard let value = value as? Bool else {
fatalError("value must be a bool")
}
return __CFBoolean(value)
}
}
// Double 专用的工厂类
class DoubleNumberFactory: NumberFactory {
func createNumber(value: Any) -> Number {
guard let value = value as? Double else {
fatalError("value must be a double")
}
return __CFNumber(value)
}
}

具体使用中,先创建工厂,然后直接根据相应的工厂创建相应的Number

1
2
3
4
5
6
7
// 先创建工厂
let boolFactory = BoolNumberFactory()
let doubleFactory = DoubleNumberFactory()

// 然后直接根据相应的工厂创建相应的Number
let boolN = boolFactory.createNumber(value: false)
let doubleN = doubleFactory.createNumber(value: 2.0)

如果想新添加一个针对Float实现Number协议的__CFFloat,添加完成后,直接再添加一个对应的NumberFactory子类即可。

1
2
3
4
5
6
7
8
9
// Float 专用的工厂类
class FloatNumberFactory: NumberFactory {
func createNumber(value: Any) -> Number {
guard let value = value as? Float else {
fatalError("value must be a float")
}
return __CFFloat(value)
}
}

这就是工厂方法模式与简单工厂模式的区别,即工厂方法模式不但抽象了产品,而且抽象了工厂。

抽象工厂模式(Abstract Factory)

工厂方法模式抽象了工厂,但只负责生产一种产品。抽象工厂模式与工厂方法模式一般无二,都是抽象了工厂和产品,只是抽象工厂模式中的抽象工厂会负责生产一种以上相关联、会一起使用的产品。
还是以Number的抽象工厂NumberFactory举例。Foundation中类似NSNumber类簇的,还有NSArray:

1
2
3
4
5
6
7
8
import UIKit
let array0 = NSArray(array: [])
let array1 = NSArray(arrayLiteral: 1, 2)
let array2 = NSArray(arrayLiteral: 1)

print(type(of:array0))
print(type(of:array1))
print(type(of:array2))

打印结果如下:

1
2
3
__NSArray0
__NSArrayI
__NSSingleObjectArrayI

定义NSArray的抽象协议并实现两个私有类__CArray0__CArrayI,为不与系统中的NSArrayArray混淆,这里取CArray

1
2
3
4
5
6
7
8
9
10
11
12
13
protocol CArray {
var count: Int { get }
// 其他公共接口
}
private class __CArray0: CArray {
var count = 0
// 其他公共接口实现

}
private class __CArrayI: CArray {
var count = 0
// 其他公共接口实现
}

定义NumberCArray的抽象工厂协议NumberAndArrayFactory

1
2
3
4
5
6
protocol NumberAndArrayFactory {
// 用来生产Number的工厂方法
func createNumber(value: Any) -> Number
// 用来生产CArray的工厂方法
func createCArray() -> CArray
}

定义抽象工厂NumberAndArrayFactory的具体实现类BoolNumberAndArray0Factory:

1
2
3
4
5
6
7
8
9
10
11
12
class BoolNumberAndArray0Factory: NumberAndArrayFactory {
func createNumber(value: Any) -> Number {
guard let value = value as? Bool else {
fatalError("value must be a bool")
}
return __CFBoolean(value)
}

func createCArray() -> CArray {
return __CArray0()
}
}

具体使用时,先创建抽象工厂NumberAndArrayFactory的具体实现类,然后在调用这个实现类上的工厂方法,创建相应的产品Number或者CArray

1
2
3
4
5
6
// 创建抽象工厂`NumberAndArrayFactory`的具体实现类
let boolNumberAndArray0Factory = BoolNumberAndArray0Factory()

// 调用工厂方法,创建相应的产品
let boolNumber = boolNumberAndArray0Factory.createNumber(true)
let 0CArray = boolNumberAndArray0Factory.createCArray()

需要注意的是,这里为了说明抽象工厂模式,抽象工厂NumberAndArrayFactory所创建的NumberCArray没有任何关联,在实际项目中,同一抽象工厂所创建的产品是关联的,一般是一起结合使用,如果不关联,也不必用抽象工厂模式。

建造者模式(builder)

建造者模式是用来隔离复杂对象的配置过程,将复杂对象的配置过程单独封装成一个builder对象,完成配置后,再获取配置完成的实例对象。

cocoa中使用建造者模式的类是NSDateComponents,

1
2
3
4
5
6
7
8
9
10
11
12
import Foundation

var builder = NSDateComponents()

builder.hour = 10
builder.day = 6
builder.month = 9
builder.year = 1940
builder.calendar = Calendar(identifier: .gregorian)

var date = builder.date
print(date!)

输出结果为:

1
1940-09-06 01:00:00 +0000

NSDateComponents相当于日期的一个builderNSDateComponents用来配置日期的各个部分,配置完成后,最终获取对应的Date日期。
NSDateComponents的实现大致如下(为避免与系统中的NSDateComponentsDateComponents混淆,这里取DateBuilder):

1
2
3
4
5
6
7
8
9
10
11
12
13
class DateBuilder {
var hour = 0
var day = 0
var month = 0
var year = 1970
var calendar = Calendar(identifier: .gregorian)

var date: Date {
// 根据date components计算日期,比较复杂,这里省略了计算过程
let calculatedDate = ...
return calculatedDate
}
}

但这使用上不能链式调用,很不方便,加上各个属性的设置方法,返回自己本身,可以实现链式调用:

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
29
30
31
32
33
class DateBuilder {
var hour = 0
var day = 0
var month = 0
var year = 1970
var calendar = Calendar(identifier: .gregorian)

var date: Date {
// 根据DateComponents计算日期,比较复杂,这里省略了计算过程
let calculatedDate = ...
return calculatedDate
}
func hour(_ hour: Int) -> DateBuilder {
self.hour = hour
return self
}
func day(_ day: Int) -> DateBuilder {
self.day = day
return self
}
func month(_ month: Int) -> DateBuilder {
self.month = month
return self
}
func year(_ year: Int) -> DateBuilder {
self.year = year
return self
}
func calendar(_ calendar: Calendar) -> DateBuilder {
self.calendar = calendar
return self
}
}

使用时很方便:

1
let date = DateBuilder().hour(1).day(2).month(12).year(2017).date

原型模式(Prototype)

原型模式其实就是一个类能够通过自身copy,创建一个内容一模一样的新实例,这在iOS的系统框架Foundation中挺常见的,NSStringNSArrayNSDictionaryNSParagraphStylecopymutableCopy方法都能复制一个新的实例,从而免去了从零创建一个复杂类的麻烦。
NSParagraphStyle,当获取到一个paragraphStyle之后,突然又想在其基础上改动同时又不想直接改变原来NSParagraphStyle,最方便的不过copy一份原来的,然后在改动。

1
2
3
4
let paragraphStyle = NSParagraphStyle.default
let mutablePara = paragraphStyle.mutableCopy() as! NSMutableParagraphStyle
mutablePara.lineSpacing = 10
mutablePara.paragraphSpacing = 5

如果想实现原型模式,在swift中直接实现NSCopyingNSMutableCopying协议即可。

单例模式(Singleton)

单例模式即一个类至始至终只有一个实例(单例类是可以新创建实例的,但一般都会用公共的那个单例实例),常用于Manager上。单例在iOS系统中十分常用,如NSParagraphStyle.defaultUIScreen.mainUIApplication.sharedUserDefaults.standardFileManager.default等都是单例。

1
2
3
4
5
let paragraphStyle = NSParagraphStyle.default
let screen = UIScreen.main
let application = UIApplication.shared
let userDefault = UserDefaults.standard
let fileManager = FileManager.default

在swift中实现一个单例模式,也是非常简单的。

1
2
3
4
5
6
7
8
class Manager {
// 单例
static let shared = Manager()
// 私有化后,这个对象只会有单例这一个实例
private init() {

}
}

上述单例,初始化方法私有化了,因此在整个APP的生命周期中,将只有一个此类的实例,即单例。
但有时,单利只是给一个默认配置而已,如果想自定义,可以完全重新初始化一个新的实例,如

1
2
3
4
5
6
7
8
class ParagraphStyle {
// 单例
static let default = ParagraphStyle()
// 没有私有化,这个对象如果有需要可以创建单例以外的新的实例
init() {

}
}

总结

创建型设计模式在iOS系统中的运用相当广泛,而我们开发中只要有一定的抽象,基本都会用到,尤其是简单工厂模式和工厂模式、单例模式,希望本文的讲解能让大家能真正理解这些开发模式,并在开发中顺利应用。

参考文章:

  1. Class Clusters
  2. 从NSArray看类簇
  3. 创建者模式-建造者模式(The Builder Pattern)
  4. 简单工厂模式(Simple Factory Pattern)
  5. 工厂方法模式(Factory Method Pattern)
  6. 抽象工厂模式(Abstract Factory)
  7. Swift中编写单例的正确方式
分享到 评论