美文网首页iOS专题资源__APP完整Demo专题iOS开发iOS Developer
《创建型设计模式》之iOS系统框架实践

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

作者: 轻墨lightink | 来源:发表于2017-03-13 23:30 被阅读198次

    原文链接: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来说,传入IntFloatDoubleCharUnsignedChar等具体numberNSNumber返回的是对应的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,直接将相应的工厂方法逻辑封装在NSNumberinit方法中即可:

    @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),

    // 新添加的具体的产品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,为不与系统中的NSArrayArray混淆,这里取CArray

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

    定义NumberCArray的抽象工厂协议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所创建的NumberCArray没有任何关联,在实际项目中,同一抽象工厂所创建的产品是关联的,一般是一起结合使用,如果不关联,也不必用抽象工厂模式。

    建造者模式(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相当于日期的一个builderNSDateComponents用来配置日期的各个部分,配置完成后,最终获取对应的Date日期。
    NSDateComponents的实现大致如下(为避免与系统中的NSDateComponentsDateComponents混淆,这里取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中挺常见的,NSStringNSArrayNSDictionaryNSParagraphStylecopymutableCopy方法都能复制一个新的实例,从而免去了从零创建一个复杂类的麻烦。
    NSParagraphStyle,当获取到一个paragraphStyle之后,突然又想在其基础上改动同时又不想直接改变原来NSParagraphStyle,最方便的不过copy一份原来的,然后在改动。

    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等都是单例。

    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

    参考文章:

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

    相关文章

      网友评论

        本文标题:《创建型设计模式》之iOS系统框架实践

        本文链接:https://www.haomeiwen.com/subject/sirtnttx.html