Decorator – Design Patterns in Swift

Introduction

When using subclassing, different subclasses extend a class in different ways. But an extension is bound to the class at compile-time and can’t be changed at run-time. As a result, the Decorator pattern borns.

The idea of the Decorator pattern is responsibilities should be added to (and removed from) an object dynamically at run-time. Therefore, it is a flexible alternative to subclassing for extending functionality should be provided.

Decorator Pattern – UML

Use Decorator Pattern in Swift

The base Component interface defines operations that can be altered by decorators.

import XCTest

protocol Component {

    func operation() -> String
}

Concrete Components provide default implementations of the operations. There might be several variations of these classes.

class ConcreteComponent: Component {

    func operation() -> String {
        return "ConcreteComponent"
    }
}

The base Decorator class follows the same interface as the other components. The primary purpose of this class is to define the wrapping interface for all concrete decorators. The default implementation of the wrapping code might include a field for storing a wrapped component and the means to initialize it.

class Decorator: Component {

    private var component: Component

    init(_ component: Component) {
        self.component = component
    }

    func operation() -> String {
        return component.operation()
    }
}

Concrete Decorators call the wrapped object and alter its result in some way. Decorators may call parent implementation of the operation, instead of calling the wrapped object directly. This approach simplifies the extension of decorator classes.

class ConcreteDecoratorA: Decorator {

    override func operation() -> String {
        return "ConcreteDecoratorA(" + super.operation() + ")"
    }
}

Decorators can execute their behavior either before or after the call to a wrapped object.

class ConcreteDecoratorB: Decorator {

    override func operation() -> String {
        return "ConcreteDecoratorB(" + super.operation() + ")"
    }
}

The client code works with all objects using the Component interface. This way it can stay independent of the concrete classes of components it works with.

class Client {

    static func someClientCode(component: Component) {
        print("Result: " + component.operation())
    }
}

Let’s see how it all works together.

class DecoratorConceptual: XCTestCase {

    func testDecoratorConceptual() {
        
        print("Client: I've got a simple component")
        let simple = ConcreteComponent()
        Client.someClientCode(component: simple)

        let decorator1 = ConcreteDecoratorA(simple)
        let decorator2 = ConcreteDecoratorB(decorator1)
        print("\nClient: Now I've got a decorated component")
        Client.someClientCode(component: decorator2)
    }
}

Note how decorators can wrap not only simple components but the other decorators as well.

Conclusion

Decorators can be recognized by creation methods or constructors that accept objects of the same class or interface as a current class. The Decorator is pretty standard in Swift code, especially in code related to streams.

Leave a Reply

Your email address will not be published. Required fields are marked *