Template Method in Swift

Template Method is one of the GoF design patterns that is widely used in any OOP language. In this article we'll take a look at Template Method implementation in Swift.

The main concept is to have a base class that outlines the algorithm of what needs to be done. Base class has several abstract methods that are required to be implemented by its concrete subclasses. These methods are called hook methods. Users of the Template Method classes only interact use the Base class that implements the algorithm steps, concrete implementations of those steps are supplied by subclasses.

Lets look at an example of such Base class. We will have a Report class that outlines an algorithm for printing reports.

class Report {

    let title: String
    let text:  [String]

    init(title: String, text: [String]) {
        self.title = title
        self.text = text
    }

    func outputReport() {
        outputStart()
        outputHead()
        outputBodyStart()
        outputBody()
        outputBodyEnd()
        outputEnd()
    }

    internal func outputStart() {
        preconditionFailure("this method needs to be overriden by concrete subscasses")
    }

    internal func outputHead() {
        preconditionFailure("this method needs to be overriden by concrete subscasses")
    }

    internal func outputBodyStart() {
        preconditionFailure("this method needs to be overriden by concrete subscasses")
    }

    private func outputBody() {
        text.forEach { (line) in
            outputLine(line)
        }
    }

    internal func outputLine(line: String) {
        preconditionFailure("this method needs to be overriden by concrete subscasses")
    }

    internal func outputBodyEnd() {
        preconditionFailure("this method needs to be overriden by concrete subscasses")
    }

    internal func outputEnd() {
        preconditionFailure("this method needs to be overriden by concrete subscasses")
    }
}

Notice how Report has a public method outputReportcall a sequence of other methods to be run when report is printed. It also has a private method outputBodythat loops over lines of text to print them. The rest of the methods are internal hook methods and raise an exception due to preconditionFailure call in them. Since Swift doesn't have a support for abstract classes this is the way for us to "mimic abstraction" and express our intention for these methods to be overridden by concrete subclasses that supply particular implementation.

Now, let's see how we can have an HTMLReport that prints our reports in HTML format:

class HTMLReport: Report {

    override func outputStart() {
        print("<html>")
    }

    override func outputHead() {
        print("<head>")
        print("     <title>\(title)</title>")
        print("</head>")
    }

    override func outputBodyStart() {
        print("<body>")
    }

    override func outputLine(line: String) {
        print("     <p>\(line)</p>")
    }

    override func outputBodyEnd() {
        print("</body>")
    }

    override func outputEnd() {
        print("</html>")
    }
}

As you can see it implements those hook methods and fills in the gaps in our algorithm.

If we create and launch our HTMLReport like this:

let htmlReport = HTMLReport(title: "This is a a great report", text: ["reporting something important 1", "reporting something important 2", "reporting something important 3", "reporting something important 4"])

htmlReport.outputReport()  

We should get a nicely formatted output:

<html>  
<head>  
     <title>This is a a great report</title>
</head>  
<body>  
     <p>reporting something important 1</p>
     <p>reporting something important 2</p>
     <p>reporting something important 3</p>
     <p>reporting something important 4</p>
</body>  
</html>  

Now let's look at another concrete subclass of Report that prints out plain text.

class PlainTextReport: Report {

    override func outputStart() {}

    override func outputHead() {
        print("==========\(title)==========")
        print()
    }

    override func outputBodyStart() {}

    override func outputLine(line: String) {
        print("\(line)")
    }

    override func outputBodyEnd() {}

    override func outputEnd() {}
}

In this case we have implemented all of the hook methods but notice that only some of them have some code others are simply empty. They are not all necessary but we have to implement them and leave them blank because otherwise parent's (abstract) class implementation of those methods will be called and raise an exception.
Running this report we get the following output:

let plainTextReport = PlainTextReport(title: "This is a a great report", text: ["reporting something important 1", "reporting something important 2", "reporting something important 3", "reporting something important 4"])

plainTextReport.outputReport()  
==========This is a a great report==========

reporting something important 1  
reporting something important 2  
reporting something important 3  
reporting something important 4  

Template Method in the Wild (in iOS and Cocoa Touch)

It is very common and useful to use Template Method pattern for View Controllers. If you have a base class view controller that implements "base algorithm" you can have other methods be hook methods that subclasses supply with concrete implementations.
In general when you subclass from UIViewContrller you already accepting the contract and are overriding life-cycle methods of "view controller algorithm" such as viewDidLoad, viewWillAppear, etc. They are not strictly adhering to Template Method Pattern in the sense that they are not abstract/hook methods and you're not going to break anything if you choose not to implement them. But they express the same idea that every UIViewController has an algorithm of its lifecycle and any subsclass can plug into it but overriding specific methods without breaking the sequience of that algorithm.

Conclusion

Template Method pattern is very useful when your algorithm is well defined and you have particular things in it vary while the sequence remains the same.

comments powered by Disqus