设计模式(Swift) - 2.单例模式、备忘录模式和策略模式

上一篇 设计模式(Swift) - 1.MVC和代理 中涉及到了三点,类图,MVC和代理模式.

  • 类图用来清晰直观的表达设计模式.
  • 作为Cocoa框架的核心结构模式,MVC怎样处理视图、模型、控制器之间的关系.
  • 将我想做的事委托给有能力的人的代理模式.

1. 单例模式(Singleton Pattern)

1.单例概述

单例限制了类的实例化,一个类只能实例化一个对象,所有对单例对象的引用都是指向了同一个对象.


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.备忘录模式概述

通过备忘录模式我们可以把某个对象保存在本地,并在适当的时候恢复出来.



备忘录模式总体来说分为三部分:

  • 发起人(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去处理,但其实还有更好的方式: 策略模式.
策略模式抽象并封装业务细节,只给出相关的策略接口作为切换.



策略模式总体来说分为三部分:

  • 策略模式的使用者: 为了统一直观的使用策略模式,我们通常会用一个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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容