Fenghai: Mr. Gong, I have recently encapsulated a class for MD5 calculation of string or file contents. Would you like to taste it?

Gong: All right, let’s hear it.

The sea wind:… Watch this.

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(a) -> String { md5 }
    func getString(a) -> String { string }
    func isError(a) -> Bool { _isError }
    
    private func makeMd5(a) {
        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()
    }
}
Copy the code

Gong: Yeah, I see. Your class is an interface that encapsulates MD5. The constructor only allows strings or file paths to be passed in, and then automatically converts to MD5. It also provides an interface for updating strings, which automatically updates MD5 as well.

Wind: Yes, but I have a new need recently. When I finish md5 computation with this object, I want to pass its computation to the new object. For example:

let md5Maker1 = Md5Maker(string: "hi")
let md5MakerCopy = Md5Maker(string: md5Maker1.getString())
Copy the code

Obviously this is a bit tedious to write, and there is also a performance overhead because you have to pass in the constructor to calculate MD5 every time.

Causeway: I see, although this can be solved by providing a new constructor argument, such as introducing an MD5 argument.

Wind: Yes, but such an introduction is obviously destructive, MD5 should be the result of internal calculation, so it is more reliable, and should not be imported from outside.

Causeway: Well, this problem is also a kind of creation problem, where we want to create a copy of the class, and for a variety of reasons, we don’t want to create it through the normal constructor. For example, performance overhead, coding is tedious, and so on.

Wind sea: Yes, that’s right. For performance and coding purposes, of course, this is not unacceptable. There’s just a better way better.

Gong: Ha, your last sentence is quite reasonable. That’s why we look at design patterns. Code can be written without design patterns. However, if you are familiar with and use design patterns, you can make your code more elegant and comfortable.

Feng Hai: Ok, you tell me, what kind of design pattern is suitable for this situation?

Gong: When it comes to creating duplicate objects, there is a class of design patterns worth referring to, called prototype patterns. Let me take a stab at your code.

class Md5Maker: NSCopying {
    private init(a){}func copy(with zone: NSZone? = nil) -> Any {
        let copyObject = Md5Maker()
        copyObject.md5 = md5
        copyObject.string = string
        copyObject._isError = _isError
        return copyObject
    }

    // The rest of the code
}
Copy the code

Wind: Let me see. Oh, you provide a copy method inside Md5Maker? Well, so when an object wants to copy itself, it can do so directly by calling copy.

Gong: Yes. The NSCopying protocol provides a copy method, which is specifically designed to allow classes to copy themselves. In addition, I did a little trick by providing a private empty constructor in Md5Maker. In this way, the external constructor of Md5Maker does not change, as it is strictly parameter introduced.

Sea of wind: Wonderful. The previous calling code immediately becomes:

let md5Maker1 = Md5Maker(string: "hi")
let md5MakerCopy = md5Maker1.copy() as! Md5Maker
Copy the code

Causeway: Yes, with the built-in copy method, Md5Maker can copy the internal state to a new class while keeping the external interface unchanged. In this way, the properties are copied directly without any additional computation, and the class creation overhead is greatly reduced.

In fact, there are better performance in other languages. For example, there is a Cloneable interface in Java that provides a built-in higher performance method of assigning values via memory copy rather than manually assigning values yourself.

But while Swift doesn’t provide this, it still doesn’t hurt the value of the prototype pattern.

Feng Hai: Good. I got it.

The prototype pattern is a type of creation pattern that is characterized by “copying” an existing instance to return a new instance, rather than creating a new instance. The copied instance is what we call a “prototype,” which is customizable.

The prototype pattern is used to create complex or time-consuming instances, where copying an existing instance makes the program run more efficiently. Or create the same type of data with the same value, but with different names.