When you start working with design patterns in Kotlin, one of the first to appear is usually the Template Method pattern. This is no coincidence: it is easy to understand, very powerful in certain contexts, and if used correctly, it can save a significant amount of code duplication.
The Template Method design pattern is a very important tool when creating our applications in Kotlin, and in this entry, we will talk about it and how to use it in our applications. The following article attempts to shed light on how it is implemented in Kotlin and when its use is recommended.
However, it is also a pattern that can become problematic if applied without criteria. In this article, I explain what the Template Method pattern is in Kotlin, how to implement it correctly, and above all, when it makes sense to use it and when it should be avoided, based on both theory and practical experience.
What is the Template Method Pattern?
The Template Method pattern is a behavioral design pattern that defines the skeleton of an algorithm in a superclass (often abstract) and leaves the specific details to be implemented by child classes.
It allows subclasses to customize specific parts of the algorithm without altering its general structure; that is, we can easily create variants of the class to reuse behavior and implement the subclass's own code.
With this, code duplication is avoided, and the class can be easily reused by adapting personalized behaviors.
A disadvantage lies in the fact that if you need to change the base class or superclass, you will likely have to change ALL related classes or subclasses.
The Template Method is a very simple design pattern that separates the parts of the class to be shared or the superclass through an abstract parent class that contains the algorithm steps and allows inherited classes to override individual steps.
Definition and Objective of the Pattern
The Template Method is a behavioral design pattern that defines the general structure of an algorithm in a base class, delegating the implementation of certain concrete steps to the subclasses.
The idea is simple:
- The algorithm flow does not change.
- Some parts of the behavior can vary.
In Kotlin, this fits naturally thanks to:
- Abstract classes.
- Abstract or open methods.
- The fact that methods are final by default.
Purpose of the Pattern
The Template Method pattern is used to:
- Avoid code duplication.
- Allow the creation of variants of a base class.
- Reuse base class logic in multiple subclasses.
The Problem it Solves in Object-Oriented Programming
This pattern appears when you have several classes that:
- Follow exactly the same flow.
- But differ in small details.
Instead of duplicating that flow in each class, the Template Method allows centralizing common logic and avoiding errors, inconsistencies, and repeated code.
This became evident when implementing a game, where different characters shared the same action cycle, but each executed their specific behavior.
How the Template Method Pattern Works
- The Concept of the Template Method
- The heart of the pattern is the template method:
a method that defines the exact order of the algorithm steps.
- The heart of the pattern is the template method:
- In Kotlin, this method:
- Is usually not marked as open.
- Controls the entire flow.
- Calls methods that subclasses must implement.
Abstract Classes and Variable Steps in Kotlin
The typical structure is:
- An abstract class.
- A public method that defines the flow.
- Abstract or open methods for variable steps.
This forces all subclasses to respect the same execution order, which is especially useful when the flow should not be modified.
Implementation of the Template Method Pattern
The Template Method pattern is implemented as follows:
- Create an abstract class (called
Template) that has a public method calledtemplateMethodand an abstract method calleddoPartOfSomethingwhich is used inside thetemplateMethod. In this way, all classes inheriting fromTemplateare forced to implement that method. - Concrete classes (for example,
Template1andTemplate2) can have different structures. Sometimes, thedoPartOfSomethingmethod can have a default implementation, and subclasses can provide specific variants instead of implementing them from scratch. - It is common to add more than one abstract method to the
Templateclass. If you want theTemplateclass to behave as a base class, use interface delegation instead of an abstract class to avoid most of the disadvantages of this pattern.
Example
Suppose we are creating a character system for a turn-based RPG. We have two types of characters: warriors and mages. Each character attacks differently. Here is the implementation in Kotlin:
abstract class Character {
fun completeTurn() {
println("Finishing a turn with ${attack()} attack")
}
protected abstract fun attack(): String
}
class Mage : Character() {
override fun attack() = "Fireball"
}
class Warrior : Character() {
override fun attack() = "Sword"
}
fun main() {
val warrior = Warrior()
warrior.completeTurn()
val mage = Mage()
mage.completeTurn()
// Compilation error: 'attack' is protected
warrior.attack()
}In this example, Character is the abstract class that defines the templateMethod (completeTurn) and the abstract method doPartOfSomething (attack). The concrete classes Mage and Warrior implement the attack method according to their own characteristics.
As you can see from the code above, the main class or superclass exposes some abstract methods that the subclass or derived class will implement. As you can see, part of the common logic—which in this case is the println("Finishing a turn with ${attack()} attack")—will be reused in the rest of the subclasses.
Here is another example:
abstract class AbstractClass { // parent class
// You cannot override this method because it is not `open`
fun templateMethod() { // the Template Method, i.e., the algorithm with ordered steps
println("executing the template method")
primitiveOperation1() // calling the algorithm steps
primitiveOperation2()
primitiveOperation3()
}
abstract fun primitiveOperation1() // algorithm step to be implemented by the concrete class
private fun primitiveOperation2() { // algorithm step that must not be overridden
println("performing abstract operation 2")
}
abstract fun primitiveOperation3() // another step
}
class ConcreteClass : AbstractClass() { // concrete class
override fun primitiveOperation1() { // implementation of the step for the concrete class
println("performing concrete operation 1")
}
override fun primitiveOperation3() {
println("performing concrete operation 3")
}
}
class AnotherConcreteClass : AbstractClass() { // another concrete class
override fun primitiveOperation1() {
println("performing concrete operation 1") // duplicated implementation, not ideal
}
override fun primitiveOperation3() {
println("performing another concrete operation 3")
}In this implementation, we use the templateMethod() in the abstract superclass. This method specifies how each method should be invoked, allowing us to have a default implementation that can be passed to inherited classes to use this template.
This method cannot be overridden by inherited classes because it is not of type open or abstract. Kotlin blocks its overriding from child classes and allows its reuse within them.
Example 2
Basic Structure with Abstract Classes
A simple example in Kotlin would be this:
abstract class Character {
fun completeTurn() {
prepare()
action()
finish()
}
protected open fun prepare() {
println("Preparando turno")
}
protected abstract fun action()
protected open fun finish() {
println("Finalizando turno")
}
}Here:
- completeTurn() is the template method.
- action() is mandatory for subclasses.
- prepare() and finish() can be reused or overridden.
Subclasses only care about their specific behavior:
class Warrior : Character() {
override fun action() {
println("Atacando con espada")
}
}
class Mage : Character() {
override fun action() {
println("Lanzando hechizo")
}
}This approach greatly reduces duplication and makes the code easier to reason about.
Real Case: Using Template Method in a Game in Kotlin
Common Behaviors and Shared Flow
The Template Method pattern fit especially well when developing a game where I needed different characters to follow the same flow of actions.
Each turn had clear steps:
- Prepare the state.
- Execute the main action.
- Resolve effects and finish.
The order did not change, but the action did. Centralizing that flow in a base class allowed me to keep the system consistent without duplicating logic.
Why it Fits Well in Turn-Based Systems or Actions
In turn-based systems:
- The flow is predictable.
- Variations are well-defined.
- Adding new characters is frequent.
Thanks to the Template Method, adding a new type of character simply consisted of implementing its specific action without touching the rest of the system. This made the code more stable and easier to extend.
Advantages of the Template Method Pattern in Kotlin
- Code Reuse
- The clearest advantage is avoiding the repetition of common logic. The entire flow lives in a single place.
- Control of Algorithm Flow
- The pattern guarantees that no one can accidentally alter the execution order, which is critical in sensitive systems like games or processing pipelines.
- Ease of Adding New Variants
- When the design is well thought out, creating new subclasses is fast and safe, something I personally appreciated when the number of characters began to grow.
Disadvantages and Risks of the Template Method
- Coupling by Inheritance
- The biggest problem with the pattern is that it depends on inheritance. Subclasses are tightly bound to the base class.
- Difficulty in Extending the Base Flow
- If you need to modify the general flow, you will probably have to review all subclasses. This can become a maintenance pain.
When it Becomes a Problem
If:
- The flow changes frequently.
- Subclasses start needing constant exceptions.
- The inheritance tree grows too large.
Then the Template Method stops being a good option.
Template Method vs Strategy in Kotlin
Key Differences
- Template Method uses inheritance and controls the flow.
- Strategy uses composition and allows swapping behaviors dynamically.
When to Choose Each
- Use Template Method when the flow is fixed.
- Use Strategy when behavior must change at runtime.
In Kotlin, Strategy is often more flexible thanks to lambdas and higher-order functions.
Modern Alternatives to Template Method in Kotlin
- Higher-order functions
- Kotlin allows defining functions that receive other functions as parameters, achieving a similar effect without inheritance.
- Extension functions as a functional template method
- A very common example is the use of extension functions to encapsulate a flow and delegate only the variable part to the consumer, which I personally consider more idiomatic in modern Kotlin for simple cases.
When to Use (and When to Avoid) the Template Method Pattern in Kotlin
- Use it when:
- The flow is stable.
- There are many variants with small differences.
- You want to impose a clear structure.
- Avoid it when:
- You need a lot of flexibility.
- The system changes constantly.
- Kotlin offers a simpler alternative with functions.
Frequently Asked Questions About the Template Method Pattern in Kotlin
- Is Template Method still recommended in modern Kotlin?
- Yes, but with judgment. It is not always the most idiomatic option.
- Can it be implemented without inheritance?
- Not exactly, although higher-order functions can cover similar cases.
- Is it a good idea to use it in video games?
- Yes, especially in turn-based systems or well-defined flows.
Conclusion
The Template Method pattern in Kotlin remains a valid tool, especially in systems with well-defined flows such as games or step-by-step processes. In my experience, when applied correctly, it can greatly simplify design and prevent errors.
However, it is not a universal solution. Kotlin offers modern alternatives that, in many cases, fit better. The key is to understand the problem before choosing the pattern.
The Template Method pattern is useful when you need to create a general structure for an algorithm and allow subclasses to customize specific parts. However, keep in mind that this pattern can be difficult to maintain and may require changes in all subclasses if you need to extend the superclass. Use it with caution and consider other alternatives if possible.