Patrones de diseño de Kotlin: Template Method explicado

- Andrés Cruz

In english

El patrón de diseño Template Method es una herramienta muy importante al momento de crear nuestra aplicaciones en Kotlin y en esta entrada hablaremos sobre el mismo y como emplearlo en nuestras aplicaciones, el siguiente artículo intenta dar luces sobre el siguiente tema cómo se implementa en Kotlin y cuándo es recomendado utilizarlo.

¿Qué es el Patrón Template Method?

El patrón Template Method es un patrón de diseño de comportamiento que define la estructura general de un algoritmo en una superclase (a menudo abstracta) y deja los detalles específicos para ser implementados por las clases hijas. Permite a las subclases personalizar partes específicas del algoritmo sin alterar su estructura general, es decir, podemos crear variantes de la clase fácilmente para reutilizar algún comportamiento y poder implementar código propio de la subclase.

Con esto, se consigue evitar la duplicidad de código, poder reutilizar fácilmente la clase adaptando comportamientos personalizados.

Como desventaja radica en que si necesitas cambiar la clase base o superclase es probable que debas de cambiar TODAS las clases relacionadas o subclases

El método de plantilla o Template Method es un patrón de diseño muy simple que separa las partes de clase a compartir o superclase mediante una clase principal abstracta que contiene los pasos del algoritmo y permita que las clases heredadas sobrescriben los pasos individuales

Propósito del Patrón

El patrón Template Method se utiliza para:

  1. Evitar la duplicación de código.
  2. Permitir la creación de variantes de una clase base.
  3. Reutilizar la lógica de la clase base en múltiples subclases.

Implementación del Patrón Template Method

El patrón Template Method se implementa de la siguiente manera:

  1. Crear una clase abstracta (llamada Template) que tenga un método público llamado templateMethod y un método abstracto llamado doPartOfSomething que se utiliza dentro del templateMethod. De esta manera, se obliga a todas las clases que heredan de Template a implementar ese método.
  2. Las clases concretas (por ejemplo, Template1 y Template2) pueden tener diferentes estructuras. A veces, el método doPartOfSomething puede tener una implementación predeterminada, y las subclases pueden proporcionar variantes específicas en lugar de implementarlas desde cero.
  3. Es común agregar más de un método abstracto a la clase Template. Si deseas que la clase Template se comporte como una clase base, utiliza la delegación de interfaces en lugar de una clase abstracta para evitar la mayoría de las desventajas de este patrón.

Ejemplo Práctico

Supongamos que estamos creando un sistema de personajes para un juego de rol por turnos. Tenemos dos tipos de personajes: guerreros y magos. Cada personaje ataca de manera diferente. Aquí está la implementación en 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()
}

En este ejemplo, Character es la clase abstracta que define el método templateMethod (completeTurn) y el método abstracto doPartOfSomething (attack). Las clases concretas Mage y Warrior implementan el método attack según sus propias características.

Como puedes apreciar del código anterior, la clase principal o superclase expone algunos métodos abstractos que la subclase o clase derivada implementará, como puedes ver, parte de la lógica común que en este caso es el println("Finishing a turn with ${attack()} attack") se reutilizará en el resto de las subclases.

Aqui tienes otro ejemplo:

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")
    }
}

En esta implementación, empleamos el método templateMethod() en la clase abstracta de la superclase, en este método se especifican como deben ser invocados cada método, y con esto, podemos tener una implementación predeterminada que podrá ser traspasado a las clases heredadas para utilizar esta plantilla.

Este método no puede ser sobrescrito mediante las clases heredadas, ya que no es de tipo open o abstracto Kotlin bloquea su sobrescritura desde las clases hijas y permite la reutilización del mismo en las clases hijas.

Conclusiones

El patrón Template Method es útil cuando necesitas crear una estructura general para un algoritmo y permitir que las subclases personalicen partes específicas. Sin embargo, ten en cuenta que este patrón puede ser difícil de mantener y puede requerir cambios en todas las subclases si necesitas extender la superclase. Utilízalo con precaución y considerar otras alternativas si es posible.

Andrés Cruz

Desarrollo con Laravel, Django, Flask, CodeIgniter, HTML5, CSS3, MySQL, JavaScript, Vue, Android, iOS, Flutter

Andrés Cruz en Udemy

Acepto recibir anuncios de interes sobre este Blog.