Decorator Design Pattern in Swift


Greetings, traveler!

We continue researching Structural Design Patterns as part of our Design Patterns course. It’s the turn of the Decorator Design Pattern. We can use this pattern to modify an object’s behavior without inheritance. A decorator is a wrapper that alters an object’s behavior to provide the desired result in a specific situation. The decorator has a similar interface to the object it wraps, which means it can be used as a wrapper for other decorators. However, it’s important not to overdo it with decorators, as this can make the code more complicated and challenging to understand.

Example of usage

Let’s take a look at an example. As before, I’d like to give an example from the fast-food world. Imagine we have a restaurant where we serve burgers to our customers. At the same time, we offer the option of adding cheese to a cheeseburger. Naturally, the price of a cheeseburger would increase if we added more cheese. Additionally, it would be helpful to change the description so that our customers know we haven’t forgotten about their small request.

Let’s create a special protocol and our cheeseburger model.

protocol CheeseburgerProtocol {
    var name: String { get }
    var price: Double { get }
}

struct Cheeseburger: CheeseburgerProtocol {
    let name: String = "Cheeseburger"
    let price: Double = 5.00
}

And now, let’s create a Decorator that will allow us to add extra cheese to the cheeseburger, automatically adjusting its price and description accordingly.

struct CheeseburgerDecorator: CheeseburgerProtocol {
    
    var name: String {
        let extraCheeseString = extraCheese == .zero ? "" : " with extra cheese"
        return burger.name + extraCheeseString
    }
    
    var price: Double {
        burger.price + (Double(extraCheese) * 0.30)
    }
    
    private let burger: CheeseburgerProtocol
    private let extraCheese: Int
    
    init(
        burger: CheeseburgerProtocol,
        extraCheese: Int
    ) {
        
        self.burger = burger
        self.extraCheese = extraCheese
    }
    
}

let cheeseburger = Cheeseburger()
let decorator = CheeseburgerDecorator(burger: cheeseburger, extraCheese: 3)

Excellent!

Conclusion

The decorator is a useful tool but should be used carefully and with knowledge to avoid creating overly complex code.

Alight then! Now that we have finished with the Decorator, we can move on to the next Design Pattern, the Facade. See you in the following article.