設計模式(Swift) - 3.觀察者模式、建造者模式

Dariel發表於2018-07-05

設計模式(Swift) - 3.觀察者模式、建造者模式

上一篇 設計模式(Swift) - 2.單例模式、備忘錄模式和策略模式中講了三種常見的設計模式.

  • 單例模式: 限制了類的例項化,一個類只能例項化一個物件,所有對單例物件的引用都是指向了同一個物件.
  • 備忘錄模式: 我們可以把某個物件儲存在本地,並在適當的時候恢復出來,app開發中最常見的應用就是使用者資料的本地快取.
  • 策略模式: 通過封裝業務分支來遮蔽業務細節,只給出相關的策略介面作為切換.

1. 觀察者模式(Observer Pattern)

1. 觀察者模式概述

觀察者模式: 一個物件的變化,能夠被另一個物件知道. 本文除了介紹基於Runtime的KVO實現及其原理,還會自己動手去現實一套觀察者模式,畢竟在swift中使用Runtime並不被推薦.

設計模式(Swift) - 3.觀察者模式、建造者模式

  • 被觀察物件(subject): 用來被監聽的可觀察物件.
  • 觀察者(observer): 用來監聽被觀察物件.

2. 基於OC Runtime的觀察者模式實現

1. 實現一個繼承自NSObject的可觀察物件
 // @objcMembers 為了給類中每個屬性新增 @objc 關鍵詞,
@objcMembers public class KVOUser: NSObject {
    dynamic var name: String

    public init(name: String) {
        self.name = name
    }
}
複製程式碼

@objcMembers 為了給類中每個屬性新增 @objc 關鍵詞,在swift4中繼承NSObject的子類的屬性不會暴露給OC的Runtime,所以只能手動新增

swift本身是門靜態語言,新增@objc 關鍵詞是為了讓屬性具有動態特性,可以動態的生成set和get方法,因為KVO就需要去操作set方法.

    // 注意kvoObserver的生命週期
    var kvoObserver: NSKeyValueObservation?
    let kvoUser = KVOUser(name: "Dariel")

    // 監聽kvoUser name屬性的變化
    kvoObserver = kvoUser.observe(\.name, options: [.initial, .new]) {
            (user, change) in
            print("User's name is \(user.name)")
    }
複製程式碼

第一個引數是路徑,這邊是簡略寫法.name, swift會自己轉成全路徑; options是 NSKeyValueObservingOptions, 這邊傳入的表示初始化的值和新的值

2. 使用繼承自NSObject的可觀察物件
 override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
      kvoUser.name = "John"
}
複製程式碼

在任何地方改變kvoUser物件的name屬性,kvoObserver的observe方法都會回撥.

3. OC Runtime的觀察者模式實現原理

那麼KVO是怎樣實現對物件屬性的監聽的呢? 當給一個物件新增KVO之後,OC會通過Runtime將這個物件的isa指標指向(未設定KVO的物件的isa指標指向該物件的類物件)自己定義的一個原類的子類類物件(NSKVONotifying_xxx),這個子類類物件的isa指標指向原來物件的類物件,並呼叫這個類物件中的set方法,然後去通知監聽器哪些值發生了改變.

KVO具體的實現原理

在Swift4中,並沒有在語言層級上支援KVO,如果要使用需要匯入Foundation和被觀察物件必須繼承自NSObject,這種實現方式顯然不夠優雅.

4. 實現一個不基於Runtime的觀察者模式

KVO的觀察者模式本質上還是通過拿到屬性的set方法去搞事情,基於這樣的原理我們可以自己實現.直接貼程式碼,新建一個Observable的swift檔案

public class Observable<Type> {
    
    // MARK: - Callback
    fileprivate class Callback {
        fileprivate weak var observer: AnyObject?
        fileprivate let options: [ObservableOptions]
        fileprivate let closure: (Type, ObservableOptions) -> Void
        
        fileprivate init(
            observer: AnyObject,
            options: [ObservableOptions],
            closure: @escaping (Type, ObservableOptions) -> Void) {
            
            self.observer = observer
            self.options = options
            self.closure = closure
        }
    }
    
    // MARK: - Properties
    public var value: Type {
        didSet {
            removeNilObserverCallbacks()
            notifyCallbacks(value: oldValue, option: .old)
            notifyCallbacks(value: value, option: .new)
        }
    }
    
    private func removeNilObserverCallbacks() {
        callbacks = callbacks.filter { $0.observer != nil }
    }
    
    private func notifyCallbacks(value: Type, option: ObservableOptions) {
        let callbacksToNotify = callbacks.filter { $0.options.contains(option) }
        callbacksToNotify.forEach { $0.closure(value, option) }
    }
    
    // MARK: - Object Lifecycle
    public init(_ value: Type) {
        self.value = value
    }
    
    // MARK: - Managing Observers
    private var callbacks: [Callback] = []
    
    
    /// 新增觀察者
    ///
    /// - Parameters:
    ///   - observer: 觀察者
    ///   - removeIfExists: 如果觀察者存在需要移除
    ///   - options: 被觀察者
    ///   - closure: 回撥
    public func addObserver(
        _ observer: AnyObject,
        removeIfExists: Bool = true,
        options: [ObservableOptions] = [.new],
        closure: @escaping (Type, ObservableOptions) -> Void) {
        
        if removeIfExists {
            removeObserver(observer)
        }
        
        let callback = Callback(observer: observer, options: options, closure: closure)
        callbacks.append(callback)
        
        if options.contains(.initial) {
            closure(value, .initial)
        }
    }
    
    public func removeObserver(_ observer: AnyObject) {
        callbacks = callbacks.filter { $0.observer !== observer }
    }
}

// MARK: - ObservableOptions
public struct ObservableOptions: OptionSet {
    
    public static let initial = ObservableOptions(rawValue: 1 << 0)
    public static let old = ObservableOptions(rawValue: 1 << 1)
    public static let new = ObservableOptions(rawValue: 1 << 2)
    
    public var rawValue: Int
    
    public init(rawValue: Int) {
        self.rawValue = rawValue
    }
}

複製程式碼

使用:

public class User {
    // 被觀察的屬性需要是Observable型別
    public let name: Observable<String>
    public init(name: String) {
        self.name = Observable(name)
    }
}
// 用來管理觀察者
public class Observer {}

var observer: Observer? // 當observer置為nil的時候,可觀察物件會自動釋放.
let user = User(name: "Made")
observer = Observer()
user.name.addObserver(observer!, options: [.new]) { name, change in     
    print("name:\(name), change:\(change)")                        
}

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {        
    user.name.value = "Amel"
}
複製程式碼

注意: 在使用過程中,如果改變value, addObserver方法不呼叫,很有可能是Observer物件已經被釋放掉了.

5. 觀察者模式的使用場景

觀察者模式一般用在MVC模式中,控制器需要監聽某個模型屬性的改變,而模型不需要知道控制器的型別,因此多個控制器可以監聽一個模型物件.

2. 建造者模式(Buidler Pattern)

1. 建造者模式概述

建造者模式可以一步步分解複雜業務場景的實現過程.

設計模式(Swift) - 3.觀察者模式、建造者模式

  • 管理者(Dircetor): 通常用來管理建造者,一般是個Controller
  • 建造者(Builder): 通常是個類,用來管理Product的建立和資料輸入
  • 產品(Product): 比較複雜的物件,可以是類或者結構體,通常是個模型

1. 建造者模式舉例

1. Product
// MARK: - Product
public struct Person {
    public let area: Area
    public let character: Character
    public let hobby: Hobby
}
extension Person: CustomStringConvertible {
    public var description: String {
        return area.rawValue
    }
}
public enum Area: String { // 來自區域
    case ShangHai
    case ShenZhen
    case HangZhou
    case Toronto
}
public struct Character: OptionSet { // 性格
    
    public static let independent = Character(rawValue: 1 << 1) // 2
    public static let ambitious = Character(rawValue: 1 << 2) // 4
    public static let outgoing = Character(rawValue: 1 << 3) // 8
    public static let unselfish = Character(rawValue: 1 << 4) // 16
    public static let expressivity = Character(rawValue: 1 << 5) // 32

    public let rawValue: Int
    public init(rawValue: Int) {
        self.rawValue = rawValue
    }
}
public struct Hobby: OptionSet { // 愛好
    
    public static let mountaineering = Hobby(rawValue: 1 << 1)
    public static let boating = Hobby(rawValue: 1 << 2)
    public static let climbing = Hobby(rawValue: 1 << 3)
    public static let running = Hobby(rawValue: 1 << 4)
    public static let camping = Hobby(rawValue: 1 << 5)
    
    public let rawValue: Int
    public init(rawValue: Int) {
        self.rawValue = rawValue
    }
}
複製程式碼

Person中定義了三個屬性,area地區,character性格,hobby愛好,其中地區只能是一個值,性格和愛好可以支援多個值.Character和Hobby可以通過傳入一個值,設定多個值.

2. Builder
// MARK: - Builder
public class PersonStatistics {
    public private(set) var area: Area = .HangZhou
    public private(set) var characters: Character = []
    public private(set) var hobbys: Hobby = []
    
    private var outOfAreas: [Area] = [.Toronto]
    
    public func addCharacter(_ character: Character) {
        characters.insert(character)
    }
    
    public func removeCharacter(_ character: Character) {
        characters.remove(character)
    }
    
    public func addHobby(_ hobby: Hobby) {
        hobbys.insert(hobby)
    }
    
    public func removeHobby(_ hobby: Hobby) {
        hobbys.remove(hobby)
    }
    
    public func setArea(_ area: Area) throws {
        guard isAvailable(area) else { throw Error.OutOfArea }
        self.area = area
    }
    
    public func build() -> Person {
        return Person(area: area, character: characters, hobby: hobbys)
    }
    
    public func isAvailable(_ area: Area) -> Bool {
        return !outOfAreas.contains(area)
    }
    
    public enum Error: Swift.Error {
        case OutOfArea
    }
}
複製程式碼

通過builder統一對Product進行管理,設定完資料之後再去建立Person物件.

3. Director
public class ManagerStatistics {

    public func createLiLeiData() throws -> Person {
        let builder = PersonStatistics()
        try builder.setArea(.HangZhou)
        builder.addCharacter(.ambitious)
        builder.addHobby([.climbing, .boating, .camping])
        return builder.build()
    }
    
    public func createLucyData() throws -> Person {
        let builder = PersonStatistics()
        try builder.setArea(.Toronto)
        builder.addCharacter([.ambitious, .independent, .outgoing])
        builder.addHobby([.boating, .climbing, .camping])
        return builder.build()
    }
}
複製程式碼

通過Director去設定builder中的資料.

 let manager = ManagerStatistics()
        
 if let Lucy = try? manager.createLucyData() {
     print(Lucy.description)
     print(Lucy.character)
     print(Lucy.hobby)
 }else {
     print("Out of area here")
 }
        
 if let Lilei = try? manager.createLiLeiData() {
     print(Lilei.description)
     print(Lilei.character)
     print(Lilei.hobby)
 }
複製程式碼

2. 建造者模式的使用注意

建造者模式是用在創造比較複雜的Product,這個Product需要設定很多值,而這用構造器又比較麻煩的情況下.如果Product比較簡單,那用構造器就好了.

3. 總結

本篇主要講了用來對物件監聽的觀察者模式和用在建立和管理複雜物件場景下的建造者模式.

示例程式碼

參考:

The Swift Programming Language (Swift 4.1)

Objective-C程式設計之道

Design Patterns by Tutorials

如有疑問,歡迎留言 :-D

相關文章