Template Method – Design Patterns in Swift

Introduction

The “template method” is implemented as a method in a base class (usually an abstract class). This method contains code for the parts of the overall algorithm that are invariant. The template ensures that the overarching algorithm is always followed. In the template method, portions of the algorithm that may vary are implemented by sending self messages that request the execution of additional helper methods. In the base class, these helper methods are given a default implementation, or none at all (that is, they may be abstract methods).

Subclasses of the base class “fill in” the empty or “variant” parts of the “template” with specific algorithms that vary from one subclass to another. It is important that subclasses do not override the template method itself.

Template Method Pattern – UML

Use Template Method Pattern in Swift

The Abstract Protocol and its extension define a template method that contains a skeleton of some algorithm, composed of calls to (usually) abstract primitive operations.

Concrete subclasses should implement these operations, but leave the template method itself intact.

import XCTest

protocol AbstractProtocol {

    /// The template method defines the skeleton of an algorithm.
    func templateMethod()

    /// These operations already have implementations.
    func baseOperation1()

    func baseOperation2()

    func baseOperation3()

    /// These operations have to be implemented in subclasses.
    func requiredOperations1()
    func requiredOperation2()

    /// These are "hooks." 
    func hook1()
    func hook2()
}

extension AbstractProtocol {

    func templateMethod() {
        baseOperation1()
        requiredOperations1()
        baseOperation2()
        hook1()
        requiredOperation2()
        baseOperation3()
        hook2()
    }

    /// These operations already have implementations.
    func baseOperation1() {
        print("AbstractProtocol says: I am doing the bulk of the work\n")
    }

    func baseOperation2() {
        print("AbstractProtocol says: But I let subclasses override some operations\n")
    }

    func baseOperation3() {
        print("AbstractProtocol says: But I am doing the bulk of the work anyway\n")
    }

    func hook1() {}
    func hook2() {}
}

Subclasses may override hooks, but it’s not mandatory since the hooks already have default (but empty) implementation. Hooks provide additional extension points in some crucial places of the algorithm.

Concrete classes have to implement all abstract operations of the base class. They can also override some operations with a default implementation.

class ConcreteClass1: AbstractProtocol {

    func requiredOperations1() {
        print("ConcreteClass1 says: Implemented Operation1\n")
    }

    func requiredOperation2() {
        print("ConcreteClass1 says: Implemented Operation2\n")
    }

    func hook2() {
        print("ConcreteClass1 says: Overridden Hook2\n")
    }
}

Usually, concrete classes override only a fraction of base class’ operations.

class ConcreteClass2: AbstractProtocol {

    func requiredOperations1() {
        print("ConcreteClass2 says: Implemented Operation1\n")
    }

    func requiredOperation2() {
        print("ConcreteClass2 says: Implemented Operation2\n")
    }

    func hook1() {
        print("ConcreteClass2 says: Overridden Hook1\n")
    }
}

The client code calls the template method to execute the algorithm. Client code does not have to know the concrete class of an object it works with, as long as it works with objects through the interface of their base class.

class Client {

    static func clientCode(use object: AbstractProtocol) {

        object.templateMethod()
    }
}

Let’s see how it all works together.

class TemplateMethodConceptual: XCTestCase {

    func test() {

        print("Same client code can work with different subclasses:\n")
        Client.clientCode(use: ConcreteClass1())

        print("\nSame client code can work with different subclasses:\n")
        Client.clientCode(use: ConcreteClass2())
    }
}

Conclusion

In summary, the Template Method can be recognized by behavioral methods that already have a “default” behavior defined by the base class. The Template Method pattern is quite common in Swift frameworks. Developers often use it to provide framework users with a simple means of extending standard functionality using inheritance.

Leave a Reply

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