‘Template Method’ Pattern in Swift

Romain Brunie
Mac O’Clock
Published in
6 min readApr 19, 2020

Definition

‘Template Method’ pattern is a behavioral design pattern which improves the communication/interaction between objects and keeps them loosely coupled.

The ‘Template Method’ pattern defines a structure with the skeleton of an algorithm and delegates the responsibility for some of its steps to subcomponents.

When should we use this pattern?

This pattern should be used when two, or more, components have similarities in the implementation of an algorithm.

Concrete example

Let’s say we are working on a data mining application that extracts information from different kind of files (Doc, CSV and PDF).

image from https://refactoring.guru/design-patterns/template-method

At this point, our application has 3 different classes to extract data from Doc, CSV and PDF files. If we take a close look at the methods used for extracting data, we will see that they follow a similar sequence: openFile → extractData → parseData → analyzeData → sendReport → closeFile.

AnalyzeData and sendReport are the only 2 functions having a default implementation regardless of the kind of file. It means there is a lot of duplication since those 2 methods are in the Doc, CSV and PDF components. All the other functions have a custom implementation.

How should we use this pattern?

This pattern defines a ‘Template Method’ grouping all the steps of our algorithm. The example above shows the use of a common series of methods to analyze different kind of files: our algorithm. Let’s think on a structure where the Doc, CSV and PDF components eliminate the duplication.

image from https://refactoring.guru/design-patterns/template-method

In this structure, we can see a DataMiner component with a set of functions that will be used by the doc, CSV and PDF subcomponents. There are 2 different ways to implement it: Protocol and Class Inheritance.

Should we use Protocol or Class Inheritance?

Protocol and class inheritance can either be used to create components with shared behavior. According to the OOP principles, polymorphic behavior and code reuse should be achieved by composition¹.

Composability is not appropriate if objects are composed with concrete types. As a result, objects are coupled, cannot be used in isolation, and just one part of the object cannot be used either. SOLID principles² help to have proper composability because they create tiny composable components (= decoupled and single purposed components). The more composable a system is, the more maintainable, extendable, testable and reusable the system becomes.

Since Swift does not support multiple class inheritance³, the problem is it might lead to containers of functionalities that break the Single Responsibility Principle. In this case, inheritance implies code reuse, but it should extend the behavior of a class (Open-closed Principle). A subclass should not depend on functionalities they do not use. So, with class inheritance, either all of the inherited functionalities should be used or none of them. If only one specific functionality is needed from its superclass, inheritance is not the solution. Swift makes complex composition with class inheritance.

A class can inherit from multiple protocols, so it can only be implemented with the needed functionalities from the different protocols. Components implement only functionalities that they use (Interface Segregation Principle) and depend on abstraction (Dependency Inversion Principle). Composition with protocols helps to have single purpose components and makes them reusable across multiple components. They can also be extended to provide protocol implementations.

For these reasons, we prefer protocol inheritance if we are building an inheritance relationship.

[1] Composition is a way to combine objects into more complex ones.

[2] SOLID Principles are OOP coding standards that intend to create understandable, maintainable and flexible software design.

[3] Class inheritance is a way to achieve polymorphic behavior through class extension.

Implementation

All of the following code sections are written in Swift and the typealias below are used for expressiveness.

typealias File     = String
typealias RawData = String
typealias `Data` = String
typealias Analysis = String

Here is an interface with a blueprint of the needed steps / methods:

/// Blueprint of all the steps/methods from the DataMiner component
protocol DataMiner {
func mine(path: String)
func openFile(path: String) -> File
func extractData(file: File) -> RawData
func parseData(data: RawData) -> `Data`
func analyzeData(analysis: `Data`) -> Analysis
func sendReport(analysis: Analysis)
func closeFile(file: File)
}

The problem with this code is the components (Doc, CSV and PDF) that are conforming to the DataMiner protocol will have to implement all of these methods. It still leads to duplication since mine, analyzeData and sendReport have the exact same code. This protocol does not resolve our duplication problem. However, Swift has a great feature, named protocol extension, which provides method implementations.

Default Implementation

/// Blueprint of the functions that will require custom implementations
protocol DataMiner {
func openFile(path: String) -> File
func extractData(file: File) -> RawData
func parseData(data: RawData) -> `Data`
func closeFile(file: File)
}
extension DataMiner {
/// TEMPLATE METHOD: series of functions defining the algorithm
func mine(path: String) {
let file = openFile(path: path)
let rawData = extractData(file: file)
let data = parseData(data: rawData)
let analysis = analyzeData(analysis: data)
sendReport(analysis: analysis)
closeFile(file: file)
}
// MARK: Default implementations func analyzeData(analysis: `Data`) -> Analysis {
print("ℹ️ analyze data")
return "analysis"
}
func sendReport(analysis: Analysis) {
print("ℹ️ send report")
}
}

Mine, analyzeData and sendReport have been removed from the methods defined in the protocol declaration. However, they have been added to the protocol extension.

As a result, components (Doc, CSV and PDF) conforming to the DataMiner protocol will not need to implement the methods mine, analyzeData and sendReport since their implementations are already provided in the protocol extension.

PDFDataMiner Implementation

// PDFDataMiner conforms to the DataMiner protocol
class PDFDataMiner: DataMiner {
init() {
// Call the template method
mine(path: "PDFFilePath")
}
func openFile(path: String) -> File {
print("📃️ open PDF File")
return "PDF file opened"
}
func extractData(file: File) -> RawData {
print("📃️ extract PDF data")
return "PDF raw data extracted"
}
func parseData(data: RawData) -> `Data` {
print("📃️ parse PDF data")
return "PDF data parsed"
}
func closeFile(file: File) {
print("📃️ close PDF File")
}
}

Run code in a Playground

Here is an Online Swift Playground so an Xcode Playground does not have to be created in order to test this implementation of the ‘Template Method’ pattern. Then, copy the code below that corresponds with the full implementation of the ‘Template Method’ pattern for our data mining application.

typealias File     = String
typealias RawData = String
typealias `Data` = String
typealias Analysis = String
/// Blueprint of the functions that will require custom implementations
protocol DataMiner {
func openFile(path: String) -> File
func extractData(file: File) -> RawData
func parseData(data: RawData) -> `Data`
func closeFile(file: File)
}
extension DataMiner {
/// TEMPLATE METHOD: series of functions defining the algorithm
func mine(path: String) {
let file = openFile(path: path)
let rawData = extractData(file: file)
let data = parseData(data: rawData)
let analysis = analyzeData(analysis: data)
sendReport(analysis: analysis)
closeFile(file: file)
}
// MARK: Default implementations func analyzeData(analysis: `Data`) -> Analysis {
print("ℹ️ analyze data")
return "analysis"
}
func sendReport(analysis: Analysis) {
print("ℹ️ send report")
}
}
// PDFDataMiner conforms to the DataMiner protocol
class PDFDataMiner: DataMiner {
init() {
// Call the template method
mine(path: "PDFFilePath")
}
func openFile(path: String) -> File {
print("📃️ open PDF File")
return "PDF file opened"
}
func extractData(file: File) -> RawData {
print("📃️ extract PDF data")
return "PDF raw data extracted"
}
func parseData(data: RawData) -> `Data` {
print("📃️ parse PDF data")
return "PDF data parsed"
}
func closeFile(file: File) {
print("📃️ close PDF File")
}
}
// Testing the PDF implementation
_ = PDFDataMiner()

Finally, paste and run the code.

--

--

Romain Brunie
Mac O’Clock

Passionate about Clean Code and Software Craftsmanship @AVIV