Mastering Swift's Development Blog

Follow Us On Twitter
  • Jon Hoffman

Decorator Pattern: Protocol Oriented Design Pattern

Updated: Sep 4



The decorator pattern is a structural design pattern that enables us to extend or alter the functionality of types without affecting the interface or behavior of the original type. This enables us to expand on an existing type without the concerns of breaking existing code.


Problem We Are Trying To Solve

We have several types that present a certain interface however we need to alter the functionality. We may be unable to change the types themselves because it could break existing code or we may not have access to the source code.


Our Solution

Our solution to this problem will be to create a wrapper class that presents the same interface as the original type but alters the functionality or expands on the existing functionality. This will leave the original types untouched but will add the functionality or interfaces that we need. We could also use Swift extensions to add additional functionality but that would add it to the original type's interface.


The Example

In this example, we have created several shapes that adopt the Shape protocol. These shape types can draw the outline of their designated shape on the screen. In our application, we now need to have the ability to fill in the shapes in as well.




As usual in our design pattern tutorials, we are going to be showing the minimal amount of code necessary to demonstrate this pattern because we want the focus of this post to be on how the decorator design pattern works and not on implementing the example.


We are going create our Shape protocol and then create three sample types that will know how to draw a shape.

protocol Shape {
    func draw()
}

struct Square: Shape {
    func draw() {}
}
struct Triangle: Shape {
    func draw() {}
}
struct Circle: Shape {
    func draw() {}
}

In order to add the ability to fill in the shapes, we do not want to alter the original types because we also want the ability to draw the shapes without filling them in. We also need to think ahead and realize that our application may need several decorators for types that adopt the Shape protocol. For example, we may need one for rounded shapes, or one to add a shadow to a shape. With this in mind, we will start off by creating a ShapeDecorator protocol.

protocol ShapeDecorator: Shape {
    init(shape: Shape)
}

This protocol inherits all the requirements from the Shape protocol and adds a requirement for an initiator that takes an instance of a type that adopts the Shape protocol. The instance of the shape type is what the shape decorator type will be wrapping. Lets look at how we would implement a shape decorator by creating our FillShape type.

struct FillShape: ShapeDecorator {
    private var shape: Shape
    init(shape: Shape) {
        self.shape = shape
    }
    
    func draw() {
        shape.draw()
        //code to fill in shape
    }
}

In the FillShape type the draw() function starts off by calling the draw() function of the shape type that we are wrapping. We would then implement the code to fill in the shape. With the FillShape type, we are leaving the original shape types intact, and using their functionality to draw the outline of our shapes. We are then adding the extra functionality to fill in the shape.


The solution that we presented here is the traditional solution used for the decorator pattern. This solution should be used when altering the functionality as demonstrated in this example where we altered how the draw() function to add the fill functionality. If we are adding new functionality we could use Swift extensions but that would add the functionality to the original type's interface.


The decorator pattern is very useful if we need to keep our original types intact, but we do need additional times that add or alters the functionality of those types. This pattern is also very useful when working with a library that we need to expand however we do not have the original source for.

masteringSwift.jpeg

Mastering Swift 5.3

The sixth edition of this bestselling book, updated to cover through version 5.3 of the Swift programming language

Amazon

Packt

pop.jpeg

Protocol Oriented Programming

Embrace the Protocol-Oriented design paradigm, for better code maintainability and increased performance, with Swift.

Amazon

Packt