Prototype [Design Patterns with Swift (Part 3)]

Fatih Öztürk
adessoTurkey
Published in
5 min readOct 10, 2023

The Prototype design pattern is one of the creational design patterns.

The Prototype design pattern is a design pattern that makes it possible to create new objects from existing prototypes. This pattern uses a prototype object to copy an object and derives the new object’s creation from this prototype.

Use Case

Here we have an Engine class that keeps volume, horsepower, and fuel information. Also, we have a Car class that keeps brand, model, and engine features.

class Engine: CustomStringConvertible {
var volume: Double
var horsepower: Int
var fuel: String

init(volume: Double, horsepower: Int, fuel: String) {
self.volume = volume
self.horsepower = horsepower
self.fuel = fuel
}

var description: String {
"\(volume) L and \(horsepower) hp \(fuel) engine"
}
}

class Car: CustomStringConvertible {
var brand: String
var model: String
var engine: Engine

init(brand: String, model: String, engine: Engine) {
self.brand = brand
self.model = model
self.engine = engine
}

var description: String {
"\(brand) \(model) with \(engine.description)"
}
}

If a copy of a Car object is created, what will happen?

let carNumber1 = Car(brand: "Mercedes", model: "E 200", engine: Engine(volume: 2.0, horsepower: 204, fuel: "Petrol"))

// If we create copy of "carNumber1" as "carNumber2"
let carNumber2 = carNumber1
carNumber2.brand = "BMW"
carNumber2.model = "5.20d"
carNumber2.engine = Engine(volume: 2.0, horsepower: 197, fuel: "Diesel")

print(carNumber1) // Prints BMW 5.20d with 2.0 L and 197 hp Diesel engine
print(carNumber2) // Prints BMW 5.20d with 2.0 L and 197 hp Diesel engine

As you see above, a carNumber1 instance is created from the Car class, and then carNumber2 is copied from carNumber1. Some identical pieces of information are set to carNumber2, such as the brand. However, in the end, carNumber1 and carNumber2 have the same data. This is because the class is a reference type. There are two ways to fix the issue: class-to-struct conversion and the Prototype design pattern.

Sometimes it is not possible to work with structs. When we work with reference type properties, we may want to use them by using a reference type class. That’s why the Prototype design pattern makes more sense.

Prototype

We can use Prototype design pattern in two different ways: by init, or by the copy method.

  1. Using init (Copy constructor)

Copying with init is a kind of using the copy constructor. This constructor, the init method, takes the object itself as an argument rather than the properties. This init method can be provided by a protocol. The Copying protocol is below:

protocol Copying {
init(copiedFrom object: Self)
}

When the Copying protocol is conformed by Engine and Car, they have a new required init method.

class Engine: CustomStringConvertible, Copying {
...

required init(copiedFrom object: Engine) {
volume = object.volume
horsepower = object.horsepower
fuel = object.fuel
}

...
}

class Car: CustomStringConvertible, Copying {
...

required init(copiedFrom object: Car) {
// 1st way
// brand = object.brand
// model = object.model
// engine = Engine(volume: object.engine.volume,
// horsepower: object.engine.horsepower,
// fuel: object.engine.fuel)

// 2nd way
brand = object.brand
model = object.model
engine = Engine(copiedFrom: object.engine)
}

...
}

The init(copiedFrom object: Self) method will now be used to copy an object as needed. These objects can now be copied even if they are reference types. The updated usage of the copying process is below:

let carNumber1 = Car(brand: "Mercedes", model: "E 200", engine: Engine(volume: 2.0, horsepower: 204, fuel: "Petrol"))

// If we create copy of "carNumber1" as "carNumber2"
let carNumber2 = Car(copiedFrom: carNumber1)
carNumber2.brand = "BMW"
carNumber2.model = "5.20d"
carNumber2.engine = Engine(volume: 2.0, horsepower: 197, fuel: "Diesel")

print(carNumber1) // Prints Mercedes E 200 with 2.0 L and 204 hp Petrol engine
print(carNumber2) // Prints BMW 5.20d with 2.0 L and 197 hp Diesel engine

2. Using copy/clone/etc. method

The difference of this method from the first method is that it uses a name like copy/clone instead of init.

protocol Copying {
func copy() -> Self
}

class Engine: CustomStringConvertible, Copying {
...

func copy() -> Self {
Engine(volume: volume, horsepower: horsepower, fuel: fuel) as! Self
}

...
}

class Car: CustomStringConvertible, Copying {
...

func copy() -> Self {
Car(brand: brand, model: model, engine: engine) as! Self
}

...
}

let carNumber1 = Car(brand: "Mercedes", model: "E 200", engine: Engine(volume: 2.0, horsepower: 204, fuel: "Petrol"))

// If we create copy of "carNumber1" as "carNumber2"
let carNumber2 = carNumber1.copy()
carNumber2.brand = "BMW"
carNumber2.model = "5.20d"
carNumber2.engine = Engine(volume: 2.0, horsepower: 197, fuel: "Diesel")

print(carNumber1) // Prints Mercedes E 200 with 2.0 L and 204 hp Petrol engine
print(carNumber2) // Prints BMW 5.20d with 2.0 L and 197 hp Diesel engine

As you see, the copy() method creates a new object by using existing properties, and then returns the object. However, downcasting is required because the copy() method returns Self. To solve this issue, associated type protocol or generic methods can be used.

[BONUS]: Instead of using your own copy() method, you can conform to Apple’s NSCopying protocol and use its copy() method.

Pros and Cons

Pros

  • Easier Object Cloning: The Prototype design pattern makes it easy to create copies of existing objects without coupling them to their concrete classes.
  • Reduced Object Creation Costs: Instead of creating a new object every time, you can reduce object creation costs by obtaining a copy of an existing object.
  • Flexibility: The Prototype pattern isolates the object creation logic and simplifies the integration of new types of objects, enhancing the application’s extensibility and adaptability.

Cons

  • Additional Complexity: Cloning objects with references to other objects may increase the complexity. Also, it may cause performance issues or memory leaks if the clone method is not implemented efficiently or correctly.

--

--

Fatih Öztürk
adessoTurkey

Computer Engineer - iOS Developer @ adesso Turkey