原文链接:http://qingmo.me/2017/03/11/creationalDesignPattern/
欢迎关注我的微博:http://weibo.com/shellhue
为了API的易用性、易维护性和健壮性,苹果工程师在iOS系统框架中其实运用了不少经典设计模式,而这些实践也正是因为良好的封装性,开发中我们虽日日相对,却也难以察觉它的存在。相对于其他生搬硬造隔靴搔痒的例子,这些我们熟悉的不能再熟悉的API方是学习设计模式的最佳案例。因此本系列拟以iOS系统框架中相关设计模式的实践为起点,探讨研究23种经典设计模式。
本文先讲述《创建型设计模式》(Creational Patterns)。
创建型设计模式是在创建一个类时用到的设计模式,总共有5种,其中工厂方法模式还可以根据实现的不同,分出简单工厂模式和工厂方法模式。
简单工厂模式(Factory Method)
iOS系统Foundation
框架中的NSNumber
所应用的就是简单工厂模式。
简单工厂模式主要解决不同情况下,需要创建不同子类,而这些子类又需要转化为公共父类让外界去使用的问题,因为这样对外接口只有一个,实际行为却因子类的具体实现而不同。拿NSNumber
来说,传入Int
、Float
、Double
、Char
和UnsignedChar
等具体number
,NSNumber
返回的是对应的NSNumber
子类,而我们使用时只知NSNumber
,不知具体的子类。
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))
输出结果为
__NSCFBoolean
__NSCFNumber
如果用简单工厂方法实现NSNumber
(为了不与系统的NSNumber
混淆,本文自己定义的NSNumber
均去掉NS
前缀,改为Number
),代码大致如下:
// 抽象产品
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
的工厂。
具体使用时,先创建工厂,然后根据需要创建具体的实体类:
// 先创建工厂
let factory = NumberFactory()
// 然后根据需要创建实体类
let boolN = factory.createNumber(value: false)
let doubleN = factory.createNumber(value: 2.0)
而由于Objective-C
的初始化方法中可以直接返回子类型,因此不必创建一个单独的工厂类NumberFactory
,直接将相应的工厂方法逻辑封装在NSNumber
的init
方法中即可:
@implementation NSNumber
- (NSNumber *)initWithBool:(BOOL)value {
return [[__NSCFBoolean alloc] initWithBool:value];
}
- (NSNumber *)initWithDouble:(double)value {
return [[__NSCFNumber alloc] initWithDouble:value];
}
@end
而在Swift
中不可能从init
初始化方法中返回一个子类。(Swift
的init
方法除了return nil
外不能有返回值)
工厂方法模式(Factory Method)
简单工厂模式中,工厂只有一个实体类NumberFactory
,每当添加新的产品(即新实现Number
协议的子类),都需要去修改这个工厂。
比如上文新添加一个针对Float
实现Number
协议的__CFFloat
(系统中的NSNumber
并没有实体子类__NSCFFloat
,而是所有的数字类型都封装为__NSCFNumber
),
// 新添加的具体的产品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
也需要改动:
// 生产工厂
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
也抽象一层,定义为一个协议:
// 抽象工厂
protocol NumberFactory {
func createNumber(value: Any) -> Number
}
然后针对不同的Number
实体子类,都定义相应的工厂NumberFactory
子类即可:
// 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
:
// 先创建工厂
let boolFactory = BoolNumberFactory()
let doubleFactory = DoubleNumberFactory()
// 然后直接根据相应的工厂创建相应的Number
let boolN = boolFactory.createNumber(value: false)
let doubleN = doubleFactory.createNumber(value: 2.0)
如果想新添加一个针对Float
实现Number
协议的__CFFloat
,添加完成后,直接再添加一个对应的NumberFactory
子类即可。
// 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
:
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))
打印结果如下:
__NSArray0
__NSArrayI
__NSSingleObjectArrayI
定义NSArray
的抽象协议并实现两个私有类__CArray0
和__CArrayI
,为不与系统中的NSArray
和Array
混淆,这里取CArray
:
protocol CArray {
var count: Int { get }
// 其他公共接口
}
private class __CArray0: CArray {
var count = 0
// 其他公共接口实现
}
private class __CArrayI: CArray {
var count = 0
// 其他公共接口实现
}
定义Number
和CArray
的抽象工厂协议NumberAndArrayFactory
:
protocol NumberAndArrayFactory {
// 用来生产Number的工厂方法
func createNumber(value: Any) -> Number
// 用来生产CArray的工厂方法
func createCArray() -> CArray
}
定义抽象工厂NumberAndArrayFactory
的具体实现类BoolNumberAndArray0Factory
:
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
:
// 创建抽象工厂`NumberAndArrayFactory`的具体实现类
let boolNumberAndArray0Factory = BoolNumberAndArray0Factory()
// 调用工厂方法,创建相应的产品
let boolNumber = boolNumberAndArray0Factory.createNumber(true)
let 0CArray = boolNumberAndArray0Factory.createCArray()
需要注意的是,这里为了说明抽象工厂模式,抽象工厂NumberAndArrayFactory
所创建的Number
和CArray
没有任何关联,在实际项目中,同一抽象工厂所创建的产品是关联的,一般是一起结合使用,如果不关联,也不必用抽象工厂模式。
建造者模式(builder)
建造者模式是用来隔离复杂对象的配置过程,将复杂对象的配置过程单独封装成一个builder
对象,完成配置后,再获取配置完成的实例对象。
cocoa中使用建造者模式的类是NSDateComponents
,
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!)
输出结果为:
1940-09-06 01:00:00 +0000
NSDateComponents
相当于日期的一个builder
,NSDateComponents
用来配置日期的各个部分,配置完成后,最终获取对应的Date
日期。
NSDateComponents
的实现大致如下(为避免与系统中的NSDateComponents
和DateComponents
混淆,这里取DateBuilder
):
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
}
}
但这使用上不能链式调用,很不方便,加上各个属性的设置方法,返回自己本身,可以实现链式调用:
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
}
}
使用时很方便:
let date = DateBuilder().hour(1).day(2).month(12).year(2017).date
原型模式(Prototype)
原型模式其实就是一个类能够通过自身copy,创建一个内容一模一样的新实例,这在iOS的系统框架Foundation
中挺常见的,NSString
、NSArray
、NSDictionary
和NSParagraphStyle
的 copy
、mutableCopy
方法都能复制一个新的实例,从而免去了从零创建一个复杂类的麻烦。
如NSParagraphStyle
,当获取到一个paragraphStyle
之后,突然又想在其基础上改动同时又不想直接改变原来NSParagraphStyle
,最方便的不过copy
一份原来的,然后在改动。
let paragraphStyle = NSParagraphStyle.default
let mutablePara = paragraphStyle.mutableCopy() as! NSMutableParagraphStyle
mutablePara.lineSpacing = 10
mutablePara.paragraphSpacing = 5
如果想实现原型模式,在swift中直接实现NSCopying
和NSMutableCopying
协议即可。
单例模式(Singleton)
单例模式即一个类至始至终只有一个实例(单例类是可以新创建实例的,但一般都会用公共的那个单例实例),常用于Manager上。单例在iOS系统中十分常用,如NSParagraphStyle.default
、 UIScreen.main
、 UIApplication.shared
、 UserDefaults.standard
和FileManager.default
等都是单例。
let paragraphStyle = NSParagraphStyle.default
let screen = UIScreen.main
let application = UIApplication.shared
let userDefault = UserDefaults.standard
let fileManager = FileManager.default
在swift中实现一个单例模式,也是非常简单的。
class Manager {
// 单例
static let shared = Manager()
// 私有化后,这个对象只会有单例这一个实例
private init() {
}
}
上述单例,初始化方法私有化了,因此在整个APP的生命周期中,将只有一个此类的实例,即单例。
但有时,单利只是给一个默认配置而已,如果想自定义,可以完全重新初始化一个新的实例,如
class ParagraphStyle {
// 单例
static let default = ParagraphStyle()
// 没有私有化,这个对象如果有需要可以创建单例以外的新的实例
init() {
}
}
总结
创建型设计模式在iOS系统中的运用相当广泛,而我们开发中只要有一定的抽象,基本都会用到,尤其是简单工厂模式和工厂模式、单例模式,希望本文的讲解能让大家能真正理解这些开发模式,并在开发中顺利应用。
欢迎关注我的微博:http://weibo.com/shellhue
参考文章:
- Class Clusters
- 从NSArray看类簇
- 创建者模式-建造者模式(The Builder Pattern)
- 简单工厂模式(Simple Factory Pattern)
- 工厂方法模式(Factory Method Pattern)
- 抽象工厂模式(Abstract Factory)
- Swift中编写单例的正确方式