設計模式(Swift) - 2.單例模式、備忘錄模式和策略模式

Dariel發表於2018-05-07

設計模式(Swift) - 2.單例模式、備忘錄模式和策略模式

上一篇 設計模式(Swift) - 1.MVC和代理 中涉及到了三點,類圖,MVC和代理模式.

  • 類圖用來清晰直觀的表達設計模式.
  • 作為Cocoa框架的核心結構模式,MVC怎樣處理檢視、模型、控制器之間的關係.
  • 將我想做的事委託給有能力的人的代理模式.

1. 單例模式(Singleton Pattern)

1.單例概述

單例限制了類的例項化,一個類只能例項化一個物件,所有對單例物件的引用都是指向了同一個物件.

設計模式(Swift) - 2.單例模式、備忘錄模式和策略模式

2.單例的使用

// 定義一個單例
final public class MySingleton {
    static let shared = MySingleton()
    private init() {}  // 私有化構造方法(如果有需要也可以去掉)
}

// 使用
let s1 = MySingleton.shared
let s2 = MySingleton.shared
// let s3 = MySingleton()  // 報錯
dc.address(o: s1)   // 0x000060400000e5e0
dc.address(o: s2)   // 0x000060400000e5e0
複製程式碼

相比OC,swift中單例的實現簡化了不少,swift中可以使用let這種方式來保證執行緒安全.

3.單例使用的優缺點

  • 優點:
    1.由於單例能保證一個物件存在,我們可以對某些操作進行統一管理,比如,app開發中使用者資訊的儲存和讀取,如後臺開發中伺服器配置資訊的管理操作.這樣可以有效的避免不必要的物件的建立和銷燬,提高開發效率.
  • 缺點:
    1. 單例模式本質上延長了物件的宣告週期,如果強引用了比較大的物件,不可避免的會造成記憶體問題,所以在適當的時候需要進行重置單例的操作.
    2. 由於單例是全域性共享,也就意味著整個專案中都可以對單例進行操作,使得出現問題比較難定位.

2. 備忘錄模式(Memento Pattern)

1.備忘錄模式概述

通過備忘錄模式我們可以把某個物件儲存在本地,並在適當的時候恢復出來.

設計模式(Swift) - 2.單例模式、備忘錄模式和策略模式
備忘錄模式總體來說分為三部分:

  • 發起人(Originator): 負責建立一個備忘錄物件,用以儲存當前的狀態,並可使用備忘錄恢復內部狀態。
  • Memento(備忘錄): 負責儲存Originator物件,在swift中由Codable實現.
  • Caretaker(管理者): 負責備忘錄的儲存與恢復工作.

Swift tips: Codable Codable是swift4推出來的新特性,所有基本型別都實現了 Codable 協議,只要自定義的物件遵守了該協議,就可以儲存和恢復所需要的物件. 本質上Codable,就是Decodable和Encodable的集合. 具體擴充可以看這裡Swift 4 踩坑之 Codable 協議

public typealias Codable = Decodable & Encodable
複製程式碼

2.備忘錄模式舉例

個人使用者資訊的本地化儲存,包括使用者token啊之類的.

1.個人資訊操作的業務邏輯:
// MARK: - Originator(發起人)
public class UserInfo: Codable {
    
    static let shared = UserInfo()
    private init() {}
    
    public var isLogin: Bool = false
    
    public var account: String?
    public var age: Int?
    
    var description: String {
        return "account:\(account ?? "為空"), age:\(age ?? 0)"
    }

}

// MARK: - 備忘錄(Memento): 負責儲存Originator物件,swift中由Codable實現


// MARK: - 管理者(CareTaker)
public class UserInfoTaker {
    
    public static let UserInforKey = "UserInfoKey"
    
    private static let decoder = JSONDecoder()
    private static let encoder = JSONEncoder()
    private static let userDefaults = UserDefaults.standard
    
    public static func save(_ p: UserInfo) throws {
        
        let data = try encoder.encode(p)
        userDefaults.set(data, forKey: UserInforKey)
    }
    
    public static func load() throws -> UserInfo {
        
        guard let data = userDefaults.data(forKey: UserInforKey),
            let userInfo = try? decoder.decode(UserInfo.self, from: data)
            else {
                throw Error.UserInfoNotFound
        }
        
        // decode生成的物件不是單例物件,需要轉換成單例物件
        // 如果你有更好的實現方式歡迎交流
        let userInfoS = UserInfo.shared
        userInfoS.account = userInfo.account
        userInfoS.age = userInfo.age
        userInfoS.isLogin = userInfo.isLogin
        
        return userInfoS
    }
    
    public enum Error: String, Swift.Error {
        case UserInfoNotFound
    }
}
複製程式碼
2.個人資訊操作:
    let userInfo = UserInfo.shared
    userInfo.isLogin = true
    userInfo.account = "132154"
    userInfo.age = 16

    // 儲存
    do {
        try UserInfoTaker.save(userInfo)
    }catch {
        print(error)
    }

    // 讀取
    do {
        let newUserInfo = try UserInfoTaker.load()
        
        dc.log(newUserInfo.description) // account:132154, age:16
        dc.address(o: newUserInfo) // 0x000060000009a400
        
    }catch {
        print(error)
    }

    dc.log(userInfo.description) // account:132154, age:16
    dc.address(o: userInfo) // 0x000060000009a400
複製程式碼

備忘錄的最大好處就是可以恢復到特定的狀態,但每次的讀寫操作需要消耗一定的系統資源,所以在某些場景下可以將單例模式和備忘錄模式結合來統一管理運算元據.

3. 策略模式(Strategy Pattern)

1.策略模式概述

在日常開發中,我們經常會碰到邏輯分支,我們一般會用 if else或者switch去處理,但其實還有更好的方式: 策略模式. 策略模式抽象並封裝業務細節,只給出相關的策略介面作為切換.

設計模式(Swift) - 2.單例模式、備忘錄模式和策略模式
策略模式總體來說分為三部分:

  • 策略模式的使用者: 為了統一直觀的使用策略模式,我們通常會用一個switch語句再做一層封裝.
  • 策略協議: 抽象出策略物件需要實現的屬性,方法.
  • 策略物件: 具體的業務邏輯實現者.

2.策略模式舉例

實現一個商場打折的例子,分為三種情況,原價購買,按照一個折扣購買,滿多少返現多少(滿100減20).

可以先思考下再看程式碼.

1.實現商場打折的業務邏輯:
// 策略協議
protocol DiscountStrategy {
    // 支付價格
    func payment(money: Double) -> Double
}


// 原價購買
class DiscountNormal: DiscountStrategy {
    func payment(money: Double) -> Double {
        return money
    }
}

// 打折
class DiscountRebate: DiscountStrategy {
    private let rebate: Double // 折扣
    
    init(rebate: Double) {
        self.rebate = rebate
    }
    func payment(money: Double) -> Double {
        return money * rebate/10.0
    }
}

// 返現
class DiscountReturn: DiscountStrategy {
    private let moneyCondition: Double // 滿
    private let moneyReturn: Double // 返

    init(moneyCondition: Double, moneyReturn: Double) {
        self.moneyCondition = moneyCondition
        self.moneyReturn = moneyReturn
    }
    
    func payment(money: Double) -> Double {
        return money - (Double(Int(money/moneyCondition)) * moneyReturn)
    }
}

// 策略列舉
enum PayMentStyle {
    case normal
    case rebate(rebate: Double)
    case `return`(moneyCondition: Double, moneyReturn: Double)
}

// 策略管理
class DiscountContext {
    var discountStrategy: DiscountStrategy?
    
    init(style: PayMentStyle) {
        switch style { // 對應的三種方式
        case .normal:
            discountStrategy = DiscountNormal()
            
        case .rebate(rebate: let money):
            discountStrategy = DiscountRebate(rebate: money)
            
        case .return(moneyCondition: let condition, moneyReturn: let `return`):
            discountStrategy = DiscountReturn(moneyCondition: condition, moneyReturn: `return`)
            
        }
    }
    
    func getResult(money: Double) -> Double {
       return discountStrategy?.payment(money: money) ?? 0
    }
}
複製程式碼
2.使用:
 let money: Double = 800
        
 let normalPrice = DiscountContext(style: .normal).getResult(money: money)
 let rebatePrice = DiscountContext(style: .rebate(rebate: 8)).getResult(money: money)
 let returnPrice = DiscountContext(style: .return(moneyCondition: 100, moneyReturn: 20)).getResult(money: money)
        
 print("正常價格:\(normalPrice)") // 正常價格:800.0
 print("打八折:\(rebatePrice)") // 打八折:640.0
 print("滿100返20:\(returnPrice)") // 滿100返20:640.0

複製程式碼

以上就是一個簡單的策略模式實現,通過DiscountContext來管理每一個DiscountStrategy.

4. 總結

主要講了三種模式,只能例項化一個物件的單例模式,可以儲存和恢復資料的備忘錄模式以及可以在複雜業務邏輯下替代if else和switch語句的策略模式.

示例程式碼

參考:

The Swift Programming Language (Swift 4.1)

Objective-C程式設計之道

Design Patterns by Tutorials

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

相關文章