The Template Method design pattern is a very important tool when creating our applications in Kotlin and in this post we will talk about it and how to use it in our applications. The following article tries to shed light on the following topic: how it is implemented in Kotlin and when it is recommended to use it.
What is the Template Method Pattern?
The Template Method pattern is a behavioral design pattern that defines the general structure of an algorithm in a (often abstract) superclass 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 some behavior and be able to implement the subclass's own code.
With this, it is possible to avoid code duplication, being able to easily reuse the class by adapting custom behaviors.
The disadvantage is that if you need to change the base class or superclass, you will probably have to change ALL the related classes or subclasses.
The Template Method is a very simple design pattern that separates the parts of a shared class or superclass using an abstract main class that contains the steps of the algorithm and allows inherited classes to override the individual steps.
Pattern Purpose
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.
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 called templateMethod and an abstract method called doPartOfSomething that is used within the templateMethod. In this way, all classes that inherit from Template are forced to implement that method.
- Concrete classes (for example, Template1 and Template2) can have different structures. Sometimes the doPartOfSomething method can have a default implementation, and subclasses can provide specific variants rather than implementing them from scratch.
- It is common to add more than one abstract method to the Template class. If you want the Template class to behave like a base class, use interface delegation instead of an abstract class to avoid most of the disadvantages of this pattern.
Practical example
Suppose we are creating a character system for a turn-based RPG. We have two types of characters: warriors and magicians. 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) method and the doPartOfSomething (attack) abstract method. The concrete classes Mage and Warrior implement the attack method according to their own characteristics.
As you can see from the previous code, 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 you have another example:
abstract class AbstractClass { // clase padre
// No puedes anular este m茅todo porque no es `open`
fun templateMethod() { // el M茅todo de Plantilla, es decir, el algoritmo con pasos ordenados
println("ejecutando el m茅todo de plantilla")
primitiveOperation1() // llamando a los pasos del algoritmo
primitiveOperation2()
primitiveOperation3()
}
abstract fun primitiveOperation1() // paso del algoritmo a ser implementado por la clase concreta
private fun primitiveOperation2() { // paso del algoritmo que no debe ser anulado
println("realizando operaci贸n abstracta 2")
}
abstract fun primitiveOperation3() // otro paso
}
class ConcreteClass : AbstractClass() { // clase concreta
override fun primitiveOperation1() { // implementaci贸n del paso para la clase concreta
println("realizando operaci贸n concreta 1")
}
override fun primitiveOperation3() {
println("realizando operaci贸n concreta 3")
}
}
class AnotherConcreteClass : AbstractClass() { // otra clase concreta
override fun primitiveOperation1() {
println("realizando operaci贸n concreta 1") // implementaci贸n duplicada, no es ideal
}
override fun primitiveOperation3() {
println("realizando otra operaci贸n concreta 3")
}
}
In this implementation, we use the templateMethod() method in the abstract class of the superclass, in this method we specify how each method should be invoked, and with this, we can have a default implementation that can be passed to the inherited classes to use this template.
This method cannot be overridden by inherited classes, since it is not of open or abstract type. Kotlin blocks its overriding from the child classes and allows its reuse in the child classes.
Conclusions
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 to all subclasses if you need to extend the superclass. Use with caution and consider other alternatives if possible.
Develop with Laravel, Django, Flask, CodeIgniter, HTML5, CSS3, MySQL, JavaScript, Vue, Android, iOS, Flutter