风海: 铜锣君,我最近封装了一个对字符串或者文件内容进行MD5
计算的类,你来品鉴品鉴?
铜锣: 好好,愿闻其翔。
风海: ……你看好了啊。
import Foundation
import UniformTypeIdentifiers
class Md5Maker {
private var md5: String = ""
private var string: String = ""
private var _isError: Bool = false
init(string: String) {
self.string = string
makeMd5()
}
init(fileUrl: URL) {
guard let string = try? String(contentsOf: fileUrl) else {
_isError = true
return
}
self.string = string
makeMd5()
}
func update(string: String) {
self.string = string
makeMd5()
}
func getMd5() -> String { md5 }
func getString() -> String { string }
func isError() -> Bool { _isError }
private func makeMd5() {
guard let data = string.data(using: .utf8) else {
_isError = true
return
}
let digest = Insecure.MD5.hash(data: data)
self.md5 = digest.map { String(format: "%02x", $0) }.joined()
}
}
铜锣: 嗯,看明白了。你这个类就是封装了MD5
的接口,构造函数只允许传入字符串或者文件路径,然后自动转成md5
,挺好。并且还提供了一个更新字符串的接口,更新字符串也会自动更新md5
。
风海: 没错,但是我最近有个新需求。当我用这个对象计算完md5
后,我希望把它的计算结果传给新的对象。例如这样:
let md5Maker1 = Md5Maker(string: "hi")
let md5MakerCopy = Md5Maker(string: md5Maker1.getString())
显然这么写比较繁琐,而且还有个性能开销问题,每次都要传入构造函数计算一次md5
。
铜锣: 理解了,虽然这可以通过提供一个新的构造函数参数来解决,比如引入一个md5
参数。
风海: 对,但是这样的引入显然是有破坏性的,md5
应该是内部计算的结果,这样它才比较可靠,而不应该由外界传入。
铜锣: 嗯,这个问题也是一种类的创建问题,我们希望创建一个类的拷贝,出于种种原因,我们不希望通过正常的构造函数来创建。比如性能开销原因,编码上的繁琐原因等。
风海: 是的,就是这样。出于性能和编码的繁琐的考虑,当然了,这种问题也不是不能接受的。就是有更好的方法当然更好啦。
铜锣: 哈,你最后一句是很有道理的。这就是为什么我们会研究设计模式,没有设计模式代码一样可以写。可是如果熟悉并活用设计模式,却可以让代码写的更优雅舒适。
风海: 好啊,你倒是说说看,这种情况适合什么设计模式?
铜锣: 关于创建重复对象问题,有一类设计模式是值得参考的,叫原型模式。
让我来给你的代码动动刀。
class Md5Maker: NSCopying {
private init() {
}
func copy(with zone: NSZone? = nil) -> Any {
let copyObject = Md5Maker()
copyObject.md5 = md5
copyObject.string = string
copyObject._isError = _isError
return copyObject
}
// 代码剩余部分
}
风海: 我看看。哦,你在Md5Maker
内部提供了一个copy
方法?嗯,这样一来当一个对象要拷贝自身时,直接通过调用copy
就能完成。
铜锣: 是的。NSCopying
协议提供了copy
方法,就是专门为了让类可以拷贝自身而提供的。另外,我还做了个小技巧,在Md5Maker
中提供了一个private
的空构造函数。这样一来Md5Maker
的外部构造函数没有变化,一样是严格的参数引入。
风海: 妙啊。那么之前的调用代码立刻变成了:
let md5Maker1 = Md5Maker(string: "hi")
let md5MakerCopy = md5Maker1.copy() as! Md5Maker
铜锣: 是的,通过内置的copy
方法,Md5Maker
可以把内部的状态复制给新的类,而对外的接口保持不变。这样一来是直接拷贝属性而不需要经过额外计算,类的创建开销就大大降低了。
事实上,在其它语言中还有更好的性能,比如在Java
中有个Cloneable
接口,它提供了内置的更高性能的通过内存拷贝赋值的办法,不需要自己手动去赋值。
但是虽然Swift
没有提供这一点,依然不妨碍原型模式的价值。
风海: 很好。学习到了。
原型模式是创建型模式的一种,其特点在于通过“复制”一个已经存在的实例来返回新的实例,而不是新建实例。被复制的实例就是我们所称的“原型”,这个原型是可定制的。
原型模式多用于创建复杂的或者耗时的实例,因为这种情况下,复制一个已经存在的实例使程序运行更高效;或者创建值相等,只是命名不一样的同类数据。
网友评论