Decorator [Design Patterns with Swift (Part 4)]

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

The Decorator design pattern is one of the structural design patterns.

The Decorator pattern is used to dynamically add additional functionality to an object without altering the functionality of a class or creating subclasses.

The Decorator design pattern is a design pattern that preserves the OCP (Open-Closed Principle). That is, it adds new functionality without changing an existing class.

Use Case

Here we have a protocol named Vehicle and a class named Car.

protocol Vehicle: CustomStringConvertible {
var description: String { get }
func price() -> Int
}

class Car: Vehicle {
var description: String = "Car"

func price() -> Int {
25000
}
}

To add properties such as color, hasParkAssist, and hasCruiseControl to classes that conform to the Vehicle protocol, this can, of course, be handled with inheritance. However, sometimes inheritance may not be allowed, such as in final classes. In that case, the Decorator design pattern can solve this problem.

Decorator

The Decorator design pattern takes the class it wants to decorate as property. Then it improves it by adding extra functionalities.

class TechnologyDecorator: Vehicle {
var vehicle: Vehicle
var hasParkAssist: Bool
var hasCruiseControl: Bool

init(vehicle: Vehicle,
hasParkAssist: Bool,
hasCruiseControl: Bool) {
self.vehicle = vehicle
self.hasParkAssist = hasParkAssist
self.hasCruiseControl = hasCruiseControl
}

var description: String {
"\(vehicle.description) with \(hasParkAssist ? "Park Assist" : ""), \(hasCruiseControl ? "Cruise Control" : "")"
}

func price() -> Int {
vehicle.price() + (hasParkAssist ? 2000 : 0) + (hasCruiseControl ? 1500 : 0)
}
}

class ColorDecorator: Vehicle {
var vehicle: Vehicle
var color: String

init(vehicle: Vehicle, color: String) {
self.vehicle = vehicle
self.color = color
}

var description: String {
"\(vehicle.description) with \(color) color"
}

func price() -> Int {
vehicle.price() + 500
}
}

As seen above, the TechnologyDecorator class wants to improve classes that conform to the Vehicle protocol. That’s why it keeps it as property. It also improves it by adding the hasParkAssist and hasCruiseControl properties. Likewise, the ColorDecorator class adds a color property to classes that conform to the Vehicle protocol. Also, both decorator classes update both the price() method and the description property according to their parameters. The usage of the decorator instances is below.

let car = Car()
print(car) // Prints Car
print(car.price()) // Prints 25000

let technologicalCar = TechnologyDecorator(vehicle: car,
hasParkAssist: true,
hasCruiseControl: true)
print(technologicalCar) // Prints Car with Park Assist, Cruise Control
print(technologicalCar.price()) // Prints 28500

let blackTechnologicalCar = ColorDecorator(vehicle: technologicalCar,
color: "Black")
print(blackTechnologicalCar) // Prints Car with Park Assist, Cruise Control with Black color
print(blackTechnologicalCar.price()) // Prints 29000

Multiple Inheritance

Using inheritance, the functionality of more than one class cannot be developed at the same time. Because multiple inheritance is not allowed in the Swift language. However, more than one class can be decorated at the same time with the decorator. This is one of the reasons why the decorator is appealing.

protocol CanFly {
func fly()
}

class Bat: CanFly {
func fly() {}
}

protocol CanRun {
func run()
}

class Man: CanRun {
func run() {}
}

// Decorator
class BatMan: CanFly, CanRun {
private let bat = Bat()
private let man = Man()

func fly() {
bat.fly()
}

func run() {
man.run()
}
}

As seen above, there are two protocols named CanFly and CanRun. They provide the fly() and run() methods. There are Bat and Man classes that conform to those. The BatMan is a decorator that adds the run() method to the Bat class, and the fly() method to the Man class. Therefore, the decorator decorated both classes at the same time using different methods. It also delegated the fly method to the bat instance and the run method to the man instance.

Pros and Cons

Pros

  • Open-Closed Principle and Flexibility: The Decorator pattern follows the open-closed principle, allowing you to add new functionality without modifying existing classes or creating new subclasses. This promotes easier code maintenance and long-term flexibility.
  • Single Responsibility: Each decorator is responsible for adding a specific functionality, ensuring that each class has a single responsibility.
  • Composite Objects: You can combine several behaviors by wrapping an object into multiple decorators.

Cons

  • Complexity: The Decorator pattern can lead to the creation of numerous small classes and interfaces, increasing complexity. This complexity can reduce code readability and comprehensibility.
  • Ordering Issues: It is tough to implement a decorator in such a way that its behavior doesn’t depend on the order in the decorators stack. Adding functionality in the wrong order can yield unexpected results.
  • Confusion: Using too many decorators can lead to code clutter, which makes it difficult to distinguish which decorator adds what functionality.

--

--

Fatih Öztürk
adessoTurkey

Computer Engineer - iOS Developer @ adesso Turkey