I love cooking, but I’m not a master chef—just someone who enjoys experimenting in the kitchen. Wait, what am I talking about? How does this relate to the Template Method design pattern? Don’t worry; we’ll uncover the mystery shortly. First, let me outline what we’ll cover in this blog. We’ll start by understanding the Template Method Pattern, explore its structure, and then dive into some real-world use cases.
So, back to cooking! As I mentioned, I love it. I often watch recipes on YouTube and try to cook accordingly. Sometimes they turn out delicious; other times, not so much. But here’s an important point I want to highlight: I never follow a recipe exactly. And I bet most of you don’t either. This is where the core idea of the Template Method design pattern comes into play.
When I watch a recipe, I use it as a guide but adapt it based on the ingredients I have or my personal preferences. There’s no strict rule that says I have to stick to the exact steps—it’s up to me to modify it as I go. Similarly, the Template Method provides a skeleton or structure for an algorithm but allows flexibility to fill in the details and decide how to proceed.
I hope you’re starting to connect the dots. Don’t worry—it will all become clearer as we proceed further.
What is the Template Method Pattern?
The Template Method Pattern is a behavioral design pattern that defines the skeleton of an algorithm in a base class and lets subclasses override specific steps without altering the algorithm’s overall structure.
Think of it as a recipe. You can follow the recipe to make a dish, but certain ingredients or techniques might vary depending on your preferences. The structure remains the same, but you get the flexibility to tweak the details.
Let me simplify that. Imagine you’re a chef creating a recipe for your restaurant. Some steps, like washing ingredients and plating the dish, are always the same. But steps like seasoning or cooking style might vary depending on the type of dish. The Template Method allows you to define this recipe in a general way while letting individual chefs (subclasses) tweak specific steps.
Template Method Pattern: Problem and Solution
Understanding the Problem: Imagine you are building a gaming application with different types of games: Chess and Soccer. Each game has the following steps:
- Initialize the game.
- Start playing.
- End the game.
While the general structure is the same, the details of each step vary for Chess and Soccer. If you were to write separate implementations for each game, you might duplicate code for the steps that are common, which violates the DRY principle (Don’t Repeat Yourself).
Solution: The Template Method pattern addresses this by providing a template (skeleton) for the algorithm in a base class. Subclasses define the specific behavior for the varying steps.
Key Principles of the Template Method Pattern
- Algorithm Structure: The base class provides a high-level structure of the algorithm.
- Customizable Steps: Subclasses implement the specific parts of the algorithm.
- Consistency and Reusability: Common steps are reused, ensuring consistency across implementations.
Structure of Template Method Pattern
Key Components of the Template Method Pattern
- Abstract Class: Contains the template method, which defines the algorithm’s structure and some default implementations of steps.
- Template Method: A method that defines the sequence of steps in the algorithm. Some steps are concrete (already implemented), while others are abstract (to be implemented by subclasses).
- Concrete Class: Implements the abstract steps to provide specific behaviors.
Think of an abstract class as the foundation for a group of related classes. The common behavior for all these classes is defined in the abstract class, while the specific details are handled by the individual subclasses. The Template Method pattern gives you a way to outline the basic structure of an algorithm in a method, while leaving certain steps for the subclasses to fill in. This lets subclasses customize parts of the algorithm without changing its overall structure.
Let’s implement the Template Method design pattern for our gaming application.
Define the Abstract Class
abstract class Game {
// Template method
fun play() {
initialize()
startPlay()
endPlay()
}
// Steps of the algorithm (template method components)
abstract fun initialize()
abstract fun startPlay()
abstract fun endPlay()
}
- The
play()
method is the template method. It defines the skeleton of the algorithm. - The steps
initialize()
,startPlay()
, andendPlay()
are abstract and must be implemented by subclasses.
Create Concrete Classes
Chess Game
class Chess : Game() {
override fun initialize() {
println("Chess Game Initialized. Set up the board.")
}
override fun startPlay() {
println("Chess Game Started. Players are thinking about their moves.")
}
override fun endPlay() {
println("Chess Game Finished. Checkmate!")
}
}
Soccer Game
class Soccer : Game() {
override fun initialize() {
println("Soccer Game Initialized. Players are on the field.")
}
override fun startPlay() {
println("Soccer Game Started. Kickoff!")
}
override fun endPlay() {
println("Soccer Game Finished. The final whistle blows.")
}
}
Use the Template Method
fun main() {
println("Playing Chess:")
val chess = Chess()
chess.play()
println("\nPlaying Soccer:")
val soccer = Soccer()
soccer.play()
}
Output
Playing Chess:
Chess Game Initialized. Set up the board.
Chess Game Started. Players are thinking about their moves.
Chess Game Finished. Checkmate!
Playing Soccer:
Soccer Game Initialized. Players are on the field.
Soccer Game Started. Kickoff!
Soccer Game Finished. The final whistle blows.
Here,
- Abstract Class:
- The
Game
class encapsulates the skeleton of the algorithm in theplay()
method. - The
play()
method ensures the steps are executed in the defined order.
- The
- Concrete Classes:
- The
Chess
andSoccer
classes override the abstract methods to provide specific implementations for each step.
- The
- Reusability:
- The
play()
method in theGame
class ensures the overall structure of the algorithm is consistent across different games.
- The
The Secret Twist of the Hook Method
In the Template Method Pattern, the Hook Method is an optional concept that adds flexibility to the algorithm. The Hook Method is a method defined in the abstract class, but it doesn’t have to do anything by default. Instead, it provides a “hook” for subclasses to override and implement custom behavior, if needed, without changing the overall flow of the algorithm.
Note: A hook is a simple method (not marked with any special ‘hook’ keyword; it’s just a regular method with hook functionality) defined in the abstract class, typically with an empty or default implementation. It enables subclasses to ‘hook into’ the algorithm at specific points if needed. Subclasses can also choose to ignore the hook if it isn’t relevant to their specific behavior.
How it works:
- The Template Method defines the skeleton of an algorithm, calling a series of steps (methods), some of which can be abstract (requiring subclasses to implement them).
- A Hook Method is a method in the abstract class that does nothing by default but can be overridden in the subclasses to add specific functionality.
Why it’s useful:
- Flexibility: It allows subclasses to optionally customize parts of the algorithm without changing the structure.
- Control: The base class controls the algorithm’s flow, while allowing subclasses to “hook” in additional behavior when needed.
abstract class CookingRecipe {
fun cook() {
prepareIngredients()
cookMainPart()
serve()
}
// Must be implemented by subclasses
abstract fun prepareIngredients()
// Default implementation, can be overridden
open fun cookMainPart() {
println("Cooking the main dish in a standard way")
}
// Hook method
open fun serve() {
println("Serving the dish")
}
}
class PastaRecipe : CookingRecipe() {
override fun prepareIngredients() {
println("Preparing pasta, sauce, and vegetables")
}
override fun cookMainPart() {
println("Cooking the pasta and sauce together")
}
// Override hook method to add custom behavior
override fun serve() {
println("Serving the pasta with extra cheese")
}
}
Here,
prepareIngredients()
is an abstract method, so subclasses must implement it.cookMainPart()
has a default implementation that can be overridden.serve()
is a hook method. It has a default behavior but can be overridden in subclasses to provide custom serving logic.
Real-World Use Case: Making a Beverage
To better understand the hook method, let’s consider another example:
Suppose we’re building a system to prepare beverages like tea and coffee. The preparation steps are generally similar:
- Boil water.
- Brew the beverage.
- Pour it into a cup.
- Add condiments.
However, the brewing and condiment steps vary between tea and coffee. This is where the Template Method Pattern and the Hook Method shine. Let’s implement it!
Define the Abstract Class
We’ll create an abstract class Beverage
that defines the template method prepareBeverage()
and includes common steps.
abstract class Beverage {
// Template method: defines the skeleton of the algorithm
fun prepareBeverage() {
boilWater()
brew()
pourInCup()
if (addCondimentsNeeded()) { // Hook to customize behavior
addCondiments()
}
}
// Common steps with default implementation
private fun boilWater() {
println("Boiling water...")
}
private fun pourInCup() {
println("Pouring beverage into the cup...")
}
// Abstract steps to be implemented by subclasses
abstract fun brew()
abstract fun addCondiments()
// Optional hook method for additional behavior customization
open fun addCondimentsNeeded(): Boolean = true
}
Here,
prepareBeverage()
: The template method, defining the algorithm in a step-by-step manner.- Common Methods: Steps like boiling water and pouring the beverage are common across all beverages, so we provide default implementations.
- Abstract Methods: Methods like
brew()
andaddCondiments()
vary based on the beverage, so subclasses must define them. - Hook Method:
addCondimentsNeeded()
allows subclasses to override behavior optionally (e.g., skipping condiments).
Create Concrete Subclasses
Now, let’s create two subclasses: Tea
and Coffee
, each with its specific implementations of brewing and adding condiments.
Tea Class
class Tea : Beverage() {
override fun brew() {
println("Steeping the tea...")
}
override fun addCondiments() {
println("Adding lemon...")
}
}
Coffee Class
class Coffee : Beverage() {
override fun brew() {
println("Brewing the coffee...")
}
override fun addCondiments() {
println("Adding milk and sugar...")
}
override fun addCondimentsNeeded(): Boolean {
// Assume the user doesn't want condiments for coffee
return false
}
}
Here,
Tea
: Implementsbrew()
by steeping tea and adds lemon as a condiment.Coffee
: Implementsbrew()
by brewing coffee and adds milk and sugar.addCondimentsNeeded()
in Coffee: Returnsfalse
to skip adding condiments. This demonstrates the use of the hook method.
Test the Template Method Pattern
Let’s use these classes to prepare beverages.
fun main() {
println("Preparing tea:")
val tea = Tea()
tea.prepareBeverage()
println("\nPreparing coffee:")
val coffee = Coffee()
coffee.prepareBeverage()
}
Output
Preparing tea:
Boiling water...
Steeping the tea...
Pouring beverage into the cup...
Adding lemon...
Preparing coffee:
Boiling water...
Brewing the coffee...
Pouring beverage into the cup...
Notice how the algorithm remains consistent, while the specific steps differ.
Advantages of the Template Method Pattern
- Code Reusability: Common logic is centralized in the base class, reducing duplication.
- Flexibility: Subclasses can customize specific steps without altering the algorithm’s structure.
- Maintainability: Changes to the algorithm can be made in one place (the template method).
When to Use the Template Method Pattern?
- When you have a set of similar processes with variations in specific steps.
- When you want to enforce a specific sequence of steps.
- When you need to provide optional hooks for behavior customization.
Conclusion
The Template Method design pattern is like creating a blueprint for an algorithm. In Kotlin, it shines when you want to reuse code while still allowing flexibility for subclasses. By defining the “recipe” in a base class and letting subclasses fill in the details, we achieve a balance of consistency and customization.
I hope this explanation helped you understand the Template Method pattern in a relatable and clear way.
Happy Hooking with the Template Method Pattern 🙂