When developing software, keeping a clean and manageable codebase is essential for efficient and sustainable growth. However, as a project’s complexity increases, it can become challenging to maintain a readable codebase. Here, the decorator design pattern comes into play.

In this article, we’ll delve into the power of decorator design patterns in Swift, learning how they can simplify our codebase and improve its structure. We’ll cover the basics of the pattern, including its definition, purpose, pros and cons, and how to effectively implement it in a project. Let’s get started!
Jump ahead:
The decorator pattern, also called the wrapper pattern, is a structural design pattern in object-oriented programming that utilizes composition, providing a flexible way to dynamically extend an object’s behavior without affecting the behavior of other objects of the same class.
The decorator pattern can also act as an alternative to the traditional method of adding behaviors through inheritance, where a new subclass is created by inheriting from an existing class.
You should use the decorator pattern when your project’s requirements call for dynamic modification of an object’s behavior, thereby making subclassing an inflexible solution.
For example, you should use the decorator pattern when you want to:
Now that we’ve covered the basics of the decorator pattern and when to use it, let’s take a closer look at how to implement it in Swift.
We’ll use an example of a pizza ordering system where customers can customize their pizza by choosing different toppings and crust types. In this scenario, we can use the decorator pattern to allow for dynamic modification of the Pizza object’s behavior based on the customer’s choices.
First, let’s define an abstract class for the base pizza, which would include methods for getting the description and cost of the pizza:
protocol Pizza {
var description: String { get }
var cost: Double { get }
}
Next, we define the concrete Pizza class that conforms to the Pizza protocol. For example, a basic cheese pizza can be represented as follows:
class CheesePizza: Pizza {
var description: String { return "Cheese pizza" }
var cost: Double { return 8.99 }
}
To allow for customization of the pizza, we’ll define decorator classes that also conform to the Pizza protocol. For instance, a ToppingDecorator might add additional toppings to the pizza as follows:
class ToppingDecorator: Pizza {
let basePizza: Pizza
let topping: String
let toppingCost: Double
init(basePizza: Pizza, topping: String, toppingCost: Double) {
self.basePizza = basePizza
self.topping = topping
self.toppingCost = toppingCost
}
var description: String {
return "\(basePizza.description), with \(topping)"
}
var cost: Double {
return basePizza.cost + toppingCost
}
}
In the code snippet above, the ToppingDecorator class takes in a base Pizza object, the topping to add, and the cost of the topping. The decorator’s description property adds the topping to the description of the base pizza, and the cost property adds the cost of the topping to the cost of the base pizza.
To use the decorator pattern, we would initialize the concrete base Pizza object, and then wrap it in one or more decorator objects to add additional toppings or crust types:
let basePizza = CheesePizza() let pepperoniPizza = ToppingDecorator(basePizza: basePizza, topping: "pepperoni", toppingCost: 1.99) let pepperoniMushroomPizza = ToppingDecorator(basePizza: pepperoniPizza, topping: "mushrooms", toppingCost: 0.99) print(pepperoniMushroomPizza.description) // "Cheese pizza, with pepperoni, with mushrooms" print(pepperoniMushroomPizza.cost) // 11.97

In the code snippet above, we can see that this approach allows for easy customization of the pizza, with new toppings or crust types easily added as new decorator classes. It also ensures that the behavior of the base pizza is not affected by the addition of new toppings, and that the cost of the pizza is correctly calculated based on the toppings added.
The decorator pattern promotes composition over inheritance, leading to more modular and maintainable code. With the decorator pattern, each decorator class is responsible for a single concern, making it easier to understand and maintain code. Additionally, with the decorator pattern, you can add new behaviors to an object without affecting the behavior of other objects of the same class, thereby making it easier to extend your codebase in a scalable way.
The decorator pattern involves additional layers of object composition and method calls. Therefore, if you have a large number of decorators or complex decorator logic, it may introduce additional complexity and performance overhead into your codebase.
As your project grows in size and complexity, having a clean and concise codebase becomes more and more essential. In this article, we explored Swift’s decorator design pattern, which can simplify your codebase by extending an object’s behavior dynamically without affecting other objects of the same class.
The decorator pattern offers a more modular and maintainable approach to customizing objects, thereby making it an excellent alternative to inheritance, which faces limitations like tight coupling.
We discussed the pros and cons of the decorator pattern, when to use it, and how it works. Finally, we explored Swift’s decorator pattern with an example pizza ordering application that allows users to customize toppings and their respective prices.
I hope you enjoyed this article, and be sure to leave a comment if you have any questions. Happy coding!
Install LogRocket via npm or script tag. LogRocket.init() must be called client-side, not
server-side
$ npm i --save logrocket
// Code:
import LogRocket from 'logrocket';
LogRocket.init('app/id');
// Add to your HTML:
<script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script>
<script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up now
From basic syntax and advanced techniques to practical applications and error handling, here’s how to use node-cron.

The Angular tree view can be hard to get right, but once you understand it, it can be quite a powerful visual representation.

Build a fast, real-time app with Relay 17 to leverage features like optimistic UI updates, GraphQL subscriptions, and seamless data syncing.

Simplify component interaction and dynamic theming in Vue 3 with defineExpose and for better control and flexibility.