VOOZH about

URL: https://blog.logrocket.com/simplifying-codebase-swift-decorator-design-pattern/

⇱ Simplify your codebase with Swift’s decorator design pattern - LogRocket Blog


2023-03-29
974
#swift
Yusuf Ahmed
163437
104
👁 Image

See how LogRocket's Galileo AI surfaces the most severe issues for you

No signup required

Check it out

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.

👁 Simplify Codebase Swift Decorator Design Pattern

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:

What is Swift’s decorator pattern?

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.

When to use the decorator pattern

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:

  • Add new functionality to an object at runtime without affecting the behavior of other objects of the same class
  • Create variations of an object with different combinations of behaviors and avoid creating many complex subclasses
  • Customize components in an application without affecting the behavior of other parts of the system
  • Avoid the limitations of inheritance, like tight coupling and inflexibility, and instead utilize composition for more modular and maintainable code

How to implement the decorator pattern in Swift

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

👁 Swift Decorator Pattern Pizza Example Output Console

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.

Pros of using the decorator pattern

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.

Cons of using the decorator pattern

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.


Over 200k developers use LogRocket to create better digital experiences

👁 Image
Learn more →

Conclusion

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!

Get set up with LogRocket's modern error tracking in minutes:

  1. Visit https://logrocket.com/signup/ to get an app ID
  2. 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>
     
  3. (Optional) Install plugins for deeper integrations with your stack:
    • Redux middleware
    • NgRx middleware
    • Vuex plugin
Get started now
👁 Image
👁 Image
👁 Image

Stop guessing about your digital experience with LogRocket

Get started for free

Recent posts:

TanStack Start RSC vs. Next.js RSC: Performance, DX, and production readiness

We built the same app in TanStack Start RSC and Next.js RSC. TanStack shipped 40% less JS and built 4x faster — but Next.js is still the safer production bet.

👁 Image
Chizaram Ken
Jun 25, 2026 ⋅ 7 min read

Frontend Wrapped H1 2026: The nine biggest storylines so far

From RSC vulnerabilities and the Vercel breach to TypeScript 7.0 Beta and AI agents — the nine frontend storylines that defined H1 2026, ranked.

👁 Image
Chizaram Ken
Jun 23, 2026 ⋅ 9 min read

I shipped AI-generated React code: 4 bugs I fixed

AI tools generate working React code fast, but miss race conditions, empty states, debouncing, and accessibility. Here’s how to catch bugs before production.

👁 Image
Temitope Oyedele
Jun 22, 2026 ⋅ 10 min read

How to build a virtual engineering team with Gemini CLI subagents

Learn how to use Gemini CLI subagents to delegate frontend, backend, testing, and docs tasks to specialized agents with guardrails and clear ownership.

👁 Image
Emmanuel John
Jun 18, 2026 ⋅ 10 min read
View all posts

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