Chapter 14: Prototype Pattern

大綱

When should you use it?

  • The prototype pattern is a creational pattern that allows an object to copy itself.

    • A copying protocol that declares copy methods.

    • A prototype class that conforms to the copying protocol.

  • two different types of copies: shallow and deep.

    • A shallow copy: creates a new object instance, but doesn’t copy its properties. Any properties that are reference types still point to the same original objects.

      • For example, whenever you copy a Swift Array, which is a struct and thereby happens automatically on assignment, a new array instance is created but its elements aren’t duplicated.

    • A deep copy: creates a new object instance and duplicates each property as well.

      • For example, if you deep copy an Array, each of its elements are copied too. Swift doesn’t provide a deep copy method on Array by default

  • When should you use it?

    • Use this pattern to enable an object to copy itself.

Playground example

  • 目標: deep copy

let monster = Monster(health: 700, level: 37)
let monster2 = monster.copy() // 檢查是否copy到每個property
print("Watch out! That monster's level is \(monster2.level)!") // “Watch out! That monster's level is 37!

let eyeball = EyeballMonster(health: 3002, level: 60,
                             redness: 999)
let eyeball2 = eyeball.copy() // 檢查是否copy到每個property
print("Eww! Its eyeball redness is \(eyeball2.redness)!") // “Eww! Its eyeball redness is 999!

let eyeballMonster3 = EyeballMonster(monster) // error: 'init' is unavailable: Call copy() instead
  • Protocol - Copy

public protocol Copying: class {
  // a required initializer, init(_ prototype: Self). This is called a copy initializer as its purpose is to create a new class instance using an existing instance.
  init(_ prototype: Self)
}

extension Copying {
  // call copy() on a conforming Copying class instance that you want to copy.
  public func copy() -> Self {
    return type(of: self).init(self)
  }
}
  • Prototype class

public class Monster: Copying {

  public var health: Int
  public var level: Int

  public init(health: Int, level: Int) {
    self.health = health
    self.level = level
  }

  // In order to satisfy Copying, you must declare init(_ prototype:) as required.
  // However, you’re allowed to mark this as convenience and call another designated initializer, which is exactly what you do.
  public required convenience init(_ monster: Monster) {
    self.init(health: monster.health, level: monster.level)
  }
}

public class EyeballMonster: Monster {

  public var redness = 0

  public init(health: Int, level: Int, redness: Int) {
    self.redness = redness
    super.init(health: health, level: level)
  }

  @available(*, unavailable, message: "Call copy() instead to create a copy")
  public required convenience init(_ prototype: Monster) {
    // This compiles fine, but it causes a runtime exception. This is due to the forced cast you performed earlier, where you called prototype as! EyeballMonster.
    let eyeballMonster = prototype as! EyeballMonster
    self.init(health: eyeballMonster.health,
              level: eyeballMonster.level,
              redness: eyeballMonster.redness)
  }
}

What should you be careful about?

  • by default it’s possible to pass a superclass instance to a subclass’s copy initializer.

    • This may not be a problem if a subclass can be fully initialized from a superclass instance. However, if the subclass adds any new properties, it may not be possible to initialize it from a superclass instance.

    • To mitigate this issue, you can mark the subclass copy initializer as “unavailable.” In response, the compiler will refuse to compile any direct calls to this method

Tutorial project

Key points

  • The prototype pattern enables an object to copy itself. It involves two types: a copying protocol and a prototype.

  • The copying protocol declares copy methods, and the prototype conforms to the protocol.

  • Foundation provides an NSCopying protocol, but it doesn’t work well in Swift. It’s easy to roll your own Copying protocol, which eliminates reliance on Foundation or any other framework entirely.

  • The key to creating a Copying protocol is creating a copy initializer with the form init(_ prototype:)

Last updated

Was this helpful?