为了API的易用性、易维护性和健壮性,苹果工程师在iOS系统框架中其实运用了不少经典设计模式,而这些实践也正是因为良好的封装性,开发中我们虽日日相对,却也难以察觉它的存在。相对于其他生搬硬造隔靴搔痒的例子,这些我们熟悉的不能再熟悉的API方是学习设计模式的最佳案例。因此本系列拟以iOS系统框架中相关设计模式的实践为起点,探讨研究23种经典设计模式。
本文先讲述《创建型设计模式》(Creational Patterns)。
创建型设计模式是在创建一个类时用到的设计模式,总共有5种,其中工厂方法模式还可以根据实现的不同,分出简单工厂模式和工厂方法模式。
简单工厂模式(Factory Method) iOS系统Foundation
框架中的NSNumber
所应用的就是简单工厂模式。
简单工厂模式主要解决不同情况下,需要创建不同子类,而这些子类又需要转化为公共父类让外界去使用的问题,因为这样对外接口只有一个,实际行为却因子类的具体实现而不同。拿NSNumber
来说,传入Int
、Float
、Double
、Char
和UnsignedChar
等具体number
,NSNumber
返回的是对应的NSNumber
子类,而我们使用时只知NSNumber
,不知具体的子类。
1 2 3 4 5 6 7 8 9 import UIKitlet 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
,直接将相应的工厂方法逻辑封装在NSNumber
的init
方法中即可:
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
初始化方法中返回一个子类。(Swift
的init
方法除了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 UIKitlet 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
,为不与系统中的NSArray
和Array
混淆,这里取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 // 其他公共接口实现 }
定义Number
和CArray
的抽象工厂协议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
所创建的Number
和CArray
没有任何关联,在实际项目中,同一抽象工厂所创建的产品是关联的,一般是一起结合使用,如果不关联,也不必用抽象工厂模式。
建造者模式(builder) 建造者模式是用来隔离复杂对象的配置过程,将复杂对象的配置过程单独封装成一个builder
对象,完成配置后,再获取配置完成的实例对象。
cocoa中使用建造者模式的类是NSDateComponents
,
1 2 3 4 5 6 7 8 9 10 11 12 import Foundationvar builder = NSDateComponents ()builder.hour = 10 builder.day = 6 builder.month = 9 builder.year = 1940 builder.calendar = Calendar (identifier: .gregorian) var date = builder.dateprint (date!)
输出结果为:
1 1940 -09 -06 01 :00 :00 +0000
NSDateComponents
相当于日期的一个builder
,NSDateComponents
用来配置日期的各个部分,配置完成后,最终获取对应的Date
日期。NSDateComponents
的实现大致如下(为避免与系统中的NSDateComponents
和DateComponents
混淆,这里取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
中挺常见的,NSString
、NSArray
、NSDictionary
和NSParagraphStyle
的 copy
、mutableCopy
方法都能复制一个新的实例,从而免去了从零创建一个复杂类的麻烦。 如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中直接实现NSCopying
和NSMutableCopying
协议即可。
单例模式(Singleton) 单例模式即一个类至始至终只有一个实例(单例类是可以新创建实例的,但一般都会用公共的那个单例实例),常用于Manager上。单例在iOS系统中十分常用,如NSParagraphStyle.default
、 UIScreen.main
、 UIApplication.shared
、 UserDefaults.standard
和FileManager.default
等都是单例。
1 2 3 4 5 let paragraphStyle = NSParagraphStyle .default let screen = UIScreen .mainlet application = UIApplication .sharedlet userDefault = UserDefaults .standardlet 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系统中的运用相当广泛,而我们开发中只要有一定的抽象,基本都会用到,尤其是简单工厂模式和工厂模式、单例模式,希望本文的讲解能让大家能真正理解这些开发模式,并在开发中顺利应用。
参考文章:
Class Clusters 从NSArray看类簇 创建者模式-建造者模式(The Builder Pattern) 简单工厂模式(Simple Factory Pattern) 工厂方法模式(Factory Method Pattern) 抽象工厂模式(Abstract Factory) Swift中编写单例的正确方式