Superhero powers with Decorators: Swift Edition

Ario Liyan
9 min readOct 24, 2022

--

In this article, you are going to take a deep dive into the Decorator pattern(also known as wrappers). I wrote this article after reading two books(Design patterns: elements of reusable object-oriented software and Head First Design Pattern) couple of Youtube videos and other articles. This article is written with Swift language in mind. This post is intended to be used as a complete reference. Good read 🤓.

From Dive into design patterns by Alexander Shevts

Table of contents

  • Description
  • Why do we need Decorators
  • Implementation (in Swift)
  • Why shouldn’t we use Decorators
  • UML Diagram and Complementary notes
  • Swift Decorators
  • Use cases

Description

The decorator pattern also known as the Wrapper pattern is one of the most used structural patterns that SwiftUI is heavily based on.

In simple terms, the decorator is a structural design pattern that gives us the ability to attach new behaviors(i.e methods and functions) to objects by wrapping the object within another object.

So let’s talk about it in more detail with an example:

Let’s say we have a simple object that we use to greet users, this object has a function called “GreetMe()” which whenever is called going to returns the string “Hey Friend”

Now we want to change the object so whenever we call the method GreetMe() it takes our name(for example “Azhman”) and returns something like “Hey Azhman”.

We want to implement this change but for some reason (we are going to talk about it later) we don’t want to change the object itself and its content(we don’t want to overload or change the main function) so what can we do then?

We use the decorator(wrapper) pattern, we wrap the object within another object, and whenever we wanted to call GreetMe() with our name we call the wrapper object and the wrapper object calls the main wrapped object and takes the returned value and modifies it based on our need and returns it to us. (bare with me 🥸)

With the decorator(wrapper) pattern we can repeat this action of wrapping indefinitely.

Note that we refer to the most inner object as the component.

It may raise the question in your mind that we wanted to use the wrapper instead of the component but they are of different types and how we can use them interchangeably then?

So the first thing here is that the decorator(wrapper) should be the same type as the object itself (in other term they should conform to the same protocol or something like that, we are going to talk about it later on).

Let’s take a look at the decorator pattern book definition:

The decorator pattern attaches additional responsibilities to an object dynamically, Decorator provides a flexible alternative to subclassing for extending functionalities Head first design pattern
Head first design pattern.

Dynamically here means the change that is applied on run time without changing the content of the object and you don’t have to set your mind to a particular set of responsibilities at compile time.

Why do we need Decorators?

Superhero power

If you have read my other articles you may remember that we discussed that sharing behaviors horizontally in a tree of inheritance is not something that can be done via inheritance.

Inheritance enables us to share behaviors vertically but not horizontally and as soon as we set our mind to share behaviors horizontally we need to look around for other solutions that enable us to do sue.

Using decorators is one of many ways in which we try to use composition rather than inheritance for both code-reuse and sharing behaviors.

So we should use the Decorator pattern when we need to be able to attach extra and new behaviors to objects at runtime without changing the code that is written within these objects.

The wrapper pattern lets us structure our business logic into different layers. We then need to create a decorator for each layer and compose objects with various combinations of this logic at runtime. The client code can treat all these objects in the same way, since they all conform to the same protocol or in some cases inherit from the same superclass.

We can also use the pattern when it’s not pleasant or impossible to extend an object’s behavior using inheritance.

For example, we may face a class with the final keyword so in this case, we can extend its behaviors through this pattern.

so to recap with decorators:

  • We can extend an object’s behavior without making a new subclass.
  • We can add or remove responsibilities from an object at runtime.
  • We can combine several behaviors by wrapping an object into multiple decorators.
  • We can divide a monolithic class that implements many possible variants of behavior into several smaller classes(Single Responsibility Principle).

Implementation (in Swift)

In this example, we are changing a gasoline-engine Toyota car to a hybrid one without changing its class by using the decorator pattern.

  1. First, define a protocol as the type of both the component and wrapper
  2. Declare your class and conform to the protocol
  3. Your decorator should be of the same type of class so it can be used instead of the component
  4. Your decorator class should take a hold of the component so you can call its function whenever it suits your needs.

Note that a decorator is meant to change the behavior of an object or function, but it does not override the behavior of the original like subclassing does.

Why shouldn’t we use Decorators?

Note:

A pattern just should solve problem. If you don’t have a problem, with which pattern is targeting, then it’s just the wrong usage of the pattern.

Patterns do not stick to industry. They stick to problems of your code (often it's about duplication of code)

For a better understanding let’s continue with the example that the Head first design pattern book provides us

In the example, we have a coffeeshop that we want to develop a program for ordering different coffees and flavors. The book suggests that we should approach this problem with the decorator pattern as below:

As you can see we have a Beverage protocol or superclass that has two methods: “getDesc()” and “cost()”. These methods are for calculating the cost of the drinks and providing the drink description. This class is our main type that both our condiments and drinks should either inherits from or conforms to… (so then we can use drinks and drinks with condiments interchangeably).

Then we have drinks concrete classes: Espresso and Decaf which both can have their implementations of cost and getDesc functions.

We have a superclass for our condiments that conforms(inherits) to the main Drink protocol(class). Then there are concrete condiments as our concrete decorators.

Our condiments are both a beverage and have a beverage (the main drink or a wrapped drink), which means they are of type Beverage and hold an object of a beverage as a wrapee

Now, whenever we want to order a caramel espresso our code wraps the espresso object with the caramel condiment, and then when we can calculate the cost by calling the wrapper’s cost function which calls the espresso cost method and takes its cost and then add its cost to it and then returns the total value to us.

You can see that in this scenario we can easily use the decorator pattern to address our needs and problems.

But you may feel that this may not be the best solution because we can have an arbitrary number of condiments and this lead to a vicious circle of wrapping objects …

Maybe the better solution would be to use the strategy pattern and have a list of different condiments in the concrete drink classes.

Design patterns are common well-thought answers to common problems, so the most important phase is to determine the problem, and if you fail to do this, you may find yourself using design patterns for the wrong reasons. Not only you are not going to make your code better but you are adding non-compulsory complexity to your system.

So to recap why we may not want to use decorators because:

  • It’s hard to remove a specific wrapper from the wrapper stack.
  • It’s hard to implement a decorator in such a way that its behavior doesn’t depend on the order in the decorators' stack.
  • The initial configuration code of layers might look pretty ugly.

UML Diagram and Complementary notes

Gang of four

As you can see in the main UML Diagram we have a base Decorator class(or protocol) and concrete decorator classes. But this is not always like this. I brought an example below so we can see different ways that decorators can show themselves.

So let’s see the code based on the UML Diagram

Swift Decorators

As you already know, SwiftUI is heavily dependent on wrappers and you can’t actually perform well without using decorators(wrappers) in SwiftUI. Here we read about a couple of the most used decorators in SwiftUI.

@main

In all programs, you always have an entry point, a place where your app should start from. Adding this wrapper to a class, struct, or enum means that it contains the entry point for the app and it should provide a static mainfunction. The function is used as an entry point to the app

@State

A property wrapper type that can read and write a value managed by SwiftUI. The state is the simplest source of truth your app can have. It is designed to contain simple value types, such as Ints, Strings, and Bools. It is not designed for more complex, reference types, such as any classes or structs you define yourself and use within your app.

@State works by re-computing the body variable of your view any time it updates. So if you have some State in your view that keeps track of an integer, and you add 1 to the integer, your State will see this and re-render the view. As the view uses this state, you will see the number on your screen update.

couple of other wrappers that you can look into it for yourself:

@StateObject @Environment @EnvironmnetObject @Published @ObservedObject

Common use cases

  • “final” keyword
  • Deprecation

The “final” keyword before a class tells the compiler that this class isn’t going to be inherited from. So in other terms, we can’t extend this class behavior via inheritance. In such cases, we can use wrappers to extend behaviors….

Deprecation is another common use case for the decorator pattern. If we have an object that we used in a couple of places and we can’t change it and replace it in all the places, the easy way to implement the changes would be using the decorator pattern. We define a decorator of the same type of class and implement the changes within the wrapper and we can then replace the component object with the decorator object in all places at once or gradually, and whenever we were ready we push the changes in all places we desire.

--

--

Ario Liyan

As an iOS developer with a passion for programming concepts. I love sharing my latest discoveries with others and sparking conversations about technology.