闲话设计模式之原型模式

风海: 铜锣君,我最近封装了一个对字符串或者文件内容进行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没有提供这一点,依然不妨碍原型模式的价值。

风海: 很好。学习到了。

原型模式是创建型模式的一种,其特点在于通过“复制”一个已经存在的实例来返回新的实例,而不是新建实例。被复制的实例就是我们所称的“原型”,这个原型是可定制的。

原型模式多用于创建复杂的或者耗时的实例,因为这种情况下,复制一个已经存在的实例使程序运行更高效;或者创建值相等,只是命名不一样的同类数据。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值