Themplate Method Design Pattern in Swift


Greetings, traveler!

Let’s move on to another Behavioral Design Pattern — the Template Method. This pattern allows us to define the general structure of an algorithm and provides the possibility of overriding some of its steps. You can use this pattern when you want clients to be able to extend only specific steps of an algorithm rather than the entire algorithm or its structure. Another use case is when you have multiple objects with almost similar logic. This way, if the logic needs to be modified, you only need to make changes in one place instead of various.

Example of usage

Consider this example: we have a task to prepare a meal. To accomplish this, we have a cooking template:

  1. We need to place the ingredients in a frying pan.
  2. We can add a specific amount of oil. 
  3. We may want to season the food with salt.
  4. We must fry the ingredients until they are cooked to our desired level of doneness.

All this is done in a particular order, as it would be foolish to start roasting the food before putting it in the pan. For this reason, we can create a particular template method in which all these functions will be called one after another. At the same time, some of these functions may not need to redefine their logic. Some functions may be overridden, but that will not always be necessary. And some functions must be implemented differently for each specific case. We will do all of this within the protocol extension.

protocol MealProtocol {
    
    /// This function already has a default implementation.
    func putInThePan()
    
    /// These functions also have a default implementation, which is empty, but this can be changed if necessary.
    func addOil()
    func addSalt()
    
    /// This function needs to be implemented individually for each case.
    func fry()
    
}

extension MealProtocol {
    
    // This is our themplate method. It defines the steps of our algorithm.
    func templateMethod() {
        putInThePan()
        addOil()
        addSalt()
        fry()
    }
    
    func putInThePan()  {
        print("Put the meal in the frying pan")
    }
    
    func addOil() {}
    func addSalt() {}
    
}

Now, let’s move on to a specific implementation. To prepare the potatoes, we must follow our cooking template. We will also add a little olive oil to enhance the flavor. However, each person can adjust the salt amount according to their preferences, so we won’t add it while cooking. We will cook the potatoes for 10 minutes on medium heat.

final class FriedPotatoes: MealProtocol {
    
    func addOil() {
        print("Add a bit of olive oil")
    }
    
    func fry() {
        print("Fry for 10 minutes on medium heat")
    }
        
}

Now, we can create a client. This will be the Kitchen.

final class Kitchen {
    
    private let meal: MealProtocol
    
    init(meal: MealProtocol) {
        self.meal = meal
    }
    
    func prepare() {
        meal.templateMethod()
    }
    
}

Now, we can use it.

let meal = FriedPotato()
let kitchen = Kitchen(meal: meal)

kitchen.prepare()

Conclusion

We came across a new design pattern – the Template Method. This is an excellent way to organize an abstract algorithm’s steps. At the same time, it allows us to modify each step individually, making the system more flexible and adaptable to different types of objects.

This concludes the discussion of this design pattern. We will now move on to the next one — the Visitor pattern.