什么是享元模式
运用共享技术有效地支持大量细粒度的对象(注:择自设计模式黑书系列)
这概念看得真是让人一脸懵逼,举个简单的例子吧,比如共享单车,一台共享单车可以服务多个人,前一个用户使用完后可以交给下一个用户使用,用户不需要单独买一辆自行车,共享单车就是上面所说的共享对象,而用户则是上面所说的细粒度对象。不但共享单车,现实生活中的公共设施都可以说成是一种享元(分享元素)模式。
标准组成
- Flyweight:享元中的抽象接口,可以接受并作用于外部状态
- ConcreteFlyweight:实现享元接口,该对象必须是可以共享的
- UnshareConcreteFlyweight:非共享对象,Flyweight 接口使共享成为可能,但并不强制共享
- FlyweightFactory:创建并管理 flyweight
结构
实现
下面以共享单车 ofo 为例,实现一个享元模式:
Flyweight:
import Cocoa
protocol OFOProtocol {
var id: String { get }
var isUse: Bool { get }
func useOFO()
func endUseOFO()
}
ConcreteFlyweight:
import Cocoa
class OFO: OFOProtocol {
var id: String
var isUse: Bool = false
init(id: String) {
self.id = id
}
func useOFO() {
isUse = true
print("use ofo")
}
func endUseOFO() {
isUse = false
}
}
FlyweightFactory:
import Cocoa
class OFOFactory: NSObject {
static var ofoCache: [String : OFOProtocol] = [:]
static func useOFO(id: String) -> OFOProtocol? {
guard let ofo = ofoCache[id] else {
let ofo = OFO(id: id)
ofo.useOFO()
ofoCache[id] = ofo
return ofo
}
if ofo.isUse {
print("using!")
return nil
}
ofo.useOFO()
ofoCache[id] = ofo
return ofo
}
}
Main 方法:
import Foundation
print(OFOFactory.useOFO(id: "1")?.id ?? "ofo using")
for _ in 0..<10 {
print(OFOFactory.useOFO(id: "1")?.id ?? "ofo using")
}
for _ in 0..<10 {
print(OFOFactory.useOFO(id: "2")?.id ?? "ofo using")
}
非标准例子
最近在写开源的聊天应用,其中的草稿功能用到了享元模式,但这里并非是完全按标准走的,没有 Flyweight,但却不影响它是一个享元模式,先上代码后解析为什么用享元模式:
class JCDraft: NSObject {
static var draftCache: Dictionary<String, String> = Dictionary()
static func update(text: String?, conversation: JMSGConversation) {
let id = JCDraft.getDraftId(conversation)
if text == nil || (text?.isEmpty)! {
UserDefaults.standard.removeObject(forKey: id)
draftCache.removeValue(forKey: id)
return
}
UserDefaults.standard.set(text!, forKey: id)
draftCache[id] = text!
}
static func getDraft(_ conversation: JMSGConversation) -> String? {
let id = JCDraft.getDraftId(conversation)
if let cache = draftCache[id] {
return cache
}
let draft = UserDefaults.standard.object(forKey: id) as? String
if draft != nil {
draftCache[id] = draft
} else {
draftCache[id] = ""
}
return draft
}
static func getDraftId(_ conversation: JMSGConversation) -> String {
var id = ""
// get id
return id
}
}
上面的 UserDefaults.standard.object(forKey: id) 其实就是充当了享元模式中的 ConcreteFlyweight。
为什么要用享元呢,场景是这样的,读取聊天会话消息时,同时读取本地是不保存有该会话的草稿消息,如果有大量消息下发时,会话就会去刷新并同时重新获取草稿消息,如果不用享元模式,那么同一个会话的多次刷新就会多次访问数据库去读取,大量读取数据库是会很影响性能的,这时通过享元就能很好解决这个问题了。
小结
享元模式在更新操作的时候会带来额外的开销,但是在空间上会节省开销,至于是否需要使用享元模式,就得通过衡量这两方面的消耗来选择了。