🚨 ¡Nueva review! ✨ Mi ratón favorito para programar: el Logitech MX Master 3S . ¡Échale un ojo! 👀

El Patrón de diseño Template Method

La receta de la yaya, pero con tu toque personal.

Escrito por domin el 6 de diciembre de 2025 · Actualizado el 8 de febrero de 2026

🍳 El Patrón Template Method: La Receta Maestra

El Patrón Template Method te monta el esqueleto de un algoritmo. Mantiene el proceso principal fijo, pero permite que las clases hijas personalicen ciertos pasos a su manera.

El problema que resuelve es este: ¿Cómo evitas duplicar código cuando tienes dos procesos que son casi idénticos menos un par de pasos intermedios?

La respuesta es crear una clase base con un método “plantilla” que llama a otros métodos (algunos ya implementados, otros abstractos). Las subclases solo tienen que implementar esos huecos.

Diagrama del Patrón Template Method.

En la imagen podemos ver que, tanto un gato doméstico como uno callejero, seguirán la misma rutina: despertarse, dormir, comer, lavarse y cazar. Pero la implementación cambia. Mientras el gato doméstico caza un ratón de juguete, el callejero caza una rata real. La acción es la misma en la rutina de ambos gatos, pero con distinta implementación.


☕ 1. Café vs Té

Imagina preparar una bebida caliente (El Algoritmo):

  1. Hervir agua (igual para todos).
  2. Preparar ingrediente (Café: filtrar. Té: infusionar).
  3. Servir en taza (igual para todos).
  4. Añadir condimentos (Café: azúcar/leche. Té: limón).

La estructura es la misma, pero los pasos 2 y 4 cambian. En vez de duplicar todo el proceso para café y para té, creas una “receta genérica” con huecos que cada bebida rellena a su manera.

Así es el Template Method. El padre define el orden y los pasos comunes, los hijos rellenan los huecos específicos.

¿Qué problema resuelve el Patrón Template Method?


🛠️ 2. Los roles del patrón

Clase Abstracta

Contiene el Método Plantilla (normalmente final) que define el esqueleto del algoritmo. Tiene métodos concretos (pasos comunes) y métodos abstractos (huecos).

Métodos Abstractos

Los pasos que cada subclase debe implementar obligatoriamente. Son los huecos de la plantilla: prepararIngrediente(), anadirCondimentos().

Hooks (Ganchos)

Métodos con implementación vacía por defecto. Las subclases pueden sobrescribirlos si quieren, pero no están obligadas. Son puntos de extensión opcionales.

El Template Method es final. Las subclases no pueden cambiar el orden ni la estructura del algoritmo. Solo pueden rellenar los huecos. Esto se llama el Principio de Hollywood: “No nos llames, nosotros te llamaremos”. La clase base llama a los métodos de la subclase, no al revés.

¿Por qué el Método Plantilla se declara como 'final'?


🧑‍💻 3. Ejemplo práctico en PHP: Bebidas calientes

Vamos a implementar la receta de café y té, incluyendo un hook para que algunas bebidas puedan añadir un paso extra opcional.

<?php

// 1. Clase Abstracta con el Template Method
abstract class BebidaCaliente
{
    // El Template Method es FINAL: nadie puede cambiar el orden
    final public function prepararReceta(): void
    {
        $this->hervirAgua();
        $this->prepararIngrediente();
        $this->servirEnTaza();
        $this->anadirCondimentos();

        // Hook: paso opcional que las subclases pueden sobreescribir
        if ($this->clienteQuiereExtras()) {
            $this->anadirExtras();
        }

        echo "   ✅ ¡Bebida lista!\n";
    }

    // Métodos concretos (pasos comunes, no se sobreescriben)
    protected function hervirAgua(): void
    {
        echo "   💧 Herviendo agua...\n";
    }

    protected function servirEnTaza(): void
    {
        echo "   ☕ Sirviendo en la taza...\n";
    }

    // Métodos abstractos (huecos obligatorios)
    abstract protected function prepararIngrediente(): void;
    abstract protected function anadirCondimentos(): void;

    // Hook: implementación vacía por defecto
    protected function clienteQuiereExtras(): bool
    {
        return false; // Por defecto no se añaden extras
    }

    protected function anadirExtras(): void
    {
        // Vacío por defecto. Las subclases pueden sobreescribir
    }
}

// 2. Clases Concretas
class Cafe extends BebidaCaliente
{
    protected function prepararIngrediente(): void
    {
        echo "   🌑 Filtrando el café...\n";
    }

    protected function anadirCondimentos(): void
    {
        echo "   🍬 Añadiendo azúcar y leche...\n";
    }

    // El café sobreescribe el hook
    protected function clienteQuiereExtras(): bool
    {
        return true;
    }

    protected function anadirExtras(): void
    {
        echo "   🍫 Espolvoreando cacao por encima.\n";
    }
}

class Te extends BebidaCaliente
{
    protected function prepararIngrediente(): void
    {
        echo "   🌿 Infusionando el té...\n";
    }

    protected function anadirCondimentos(): void
    {
        echo "   🍋 Añadiendo limón...\n";
    }

    // El té NO sobreescribe el hook → no se añaden extras
}

// 3. Uso
echo "--- Preparando un Café ---\n";
$cafe = new Cafe();
$cafe->prepararReceta();

echo "\n--- Preparando un Té ---\n";
$te = new Te();
$te->prepararReceta();

// Salida:
// --- Preparando un Café ---
//    💧 Herviendo agua...
//    🌑 Filtrando el café...
//    ☕ Sirviendo en la taza...
//    🍬 Añadiendo azúcar y leche...
//    🍫 Espolvoreando cacao por encima.
//    ✅ ¡Bebida lista!
//
// --- Preparando un Té ---
//    💧 Herviendo agua...
//    🌿 Infusionando el té...
//    ☕ Sirviendo en la taza...
//    🍋 Añadiendo limón...
//    ✅ ¡Bebida lista!

Fíjate en los tres tipos de métodos:

Los hooks son la diferencia entre un Template Method rígido y uno flexible. Sin hooks, todas las subclases siguen exactamente los mismos pasos. Con hooks, tienes puntos de extensión opcionales.

¿Qué es un Hook (Gancho) en Template Method?


🧑‍💻 4. Ejemplo avanzado: Framework de tests

Vamos a ver un caso más real: un mini framework de testing donde cada test sigue la misma estructura (setup → execute → assert → teardown) pero los detalles cambian.

<?php

abstract class TestCase
{
    private string $nombre;
    private bool $paso = false;

    public function __construct(string $nombre)
    {
        $this->nombre = $nombre;
    }

    // Template Method: el flujo de un test
    final public function ejecutar(): void
    {
        echo "🧪 Test: {$this->nombre}\n";

        try {
            $this->setUp();
            $this->execute();
            $this->assert();
            $this->paso = true;
            echo "   ✅ PASS\n";
        } catch (\Exception $e) {
            echo "   ❌ FAIL: {$e->getMessage()}\n";
        } finally {
            $this->tearDown();
            echo "   🧹 Limpieza completada.\n";
        }
    }

    public function hasPasado(): bool
    {
        return $this->paso;
    }

    // Métodos abstractos: cada test los implementa
    abstract protected function execute(): void;
    abstract protected function assert(): void;

    // Hooks con implementación por defecto
    protected function setUp(): void
    {
        // Por defecto no hace nada. Sobreescribe si necesitas preparación
    }

    protected function tearDown(): void
    {
        // Por defecto no hace nada. Sobreescribe si necesitas limpieza
    }
}

// Test concreto: verificar conexión a BD
class TestConexionBD extends TestCase
{
    private ?\PDO $conexion = null;

    protected function setUp(): void
    {
        echo "   📋 setUp: Preparando conexión de test...\n";
        // Simula crear conexión
        $this->conexion = new \stdClass(); // Simulado
    }

    protected function execute(): void
    {
        echo "   🔄 Ejecutando query de prueba...\n";
        // Simula ejecutar una consulta
    }

    protected function assert(): void
    {
        if ($this->conexion === null) {
            throw new \Exception("La conexión no se estableció");
        }
        echo "   🔍 Verificando que la conexión existe...\n";
    }

    protected function tearDown(): void
    {
        echo "   🗑️ tearDown: Cerrando conexión de test...\n";
        $this->conexion = null;
    }
}

// Test concreto: verificar cálculo
class TestCalculadora extends TestCase
{
    private float $resultado = 0;

    // No necesita setUp ni tearDown → usa los hooks por defecto (vacíos)

    protected function execute(): void
    {
        echo "   🔄 Calculando 2 + 2...\n";
        $this->resultado = 2 + 2;
    }

    protected function assert(): void
    {
        if ($this->resultado !== 4.0) {
            throw new \Exception("Se esperaba 4, se obtuvo {$this->resultado}");
        }
        echo "   🔍 Verificando que 2+2 = 4...\n";
    }
}

// Uso
$tests = [
    new TestConexionBD('Conexión a base de datos'),
    new TestCalculadora('Suma básica'),
];

foreach ($tests as $test) {
    $test->ejecutar();
    echo "\n";
}

// Salida:
// 🧪 Test: Conexión a base de datos
//    📋 setUp: Preparando conexión de test...
//    🔄 Ejecutando query de prueba...
//    🔍 Verificando que la conexión existe...
//    ✅ PASS
//    🗑️ tearDown: Cerrando conexión de test...
//    🧹 Limpieza completada.
//
// 🧪 Test: Suma básica
//    🔄 Calculando 2 + 2...
//    🔍 Verificando que 2+2 = 4...
//    ✅ PASS
//    🧹 Limpieza completada.

Esto es exactamente cómo funcionan PHPUnit, JUnit y prácticamente todos los frameworks de testing: setUp()test()tearDown(). El framework (clase base) controla el flujo, tú (subclase) solo rellenas los pasos. Template Method literal bro.

Fíjate en que TestCalculadora no sobreescribe setUp() ni tearDown() porque no los necesita. Eso son hooks funcionando: opcionales, con valor por defecto.

¿Qué principio se conoce como 'No nos llames, nosotros te llamaremos'?


🌍 5. Casos de uso en el mundo real

Frameworks de testing (PHPUnit, JUnit)

setUp()test()tearDown(). El framework controla el flujo, tú implementas los pasos. Template Method puro y duro.

Frameworks web (Laravel, Symfony)

El ciclo request → middleware → controller → response lo define el framework. Tú solo implementas el controller. El framework te llama a ti, no al revés (Hollywood Principle).

Exportación de datos (PDF, CSV, Excel)

Abrir → escribir cabecera → escribir filas → escribir pie → cerrar. El esqueleto es igual, solo cambia cómo se escribe en cada formato.

Ciclos de vida (React, Android)

onCreate()onStart()onResume()onPause()onDestroy(). El SO/framework llama a los hooks, tú los sobreescribes. Template Method por todas partes.

¿Cuál es un ejemplo real de Template Method?


🔄 6. Comparativa con otros patrones

PatrónPropósitoDiferencia clave
Template MethodDefinir esqueleto de algoritmo con pasos variablesUsa herencia. El padre controla el flujo, los hijos rellenan huecos
StrategyIntercambiar algoritmos completosUsa composición. Reemplaza el algoritmo entero. No hay esqueleto fijo
Factory MethodDelegar la creación de objetos a subclasesFactory Method es un caso particular de Template Method donde el paso variable es la creación de un objeto
BuilderConstruir objetos complejos paso a pasoBuilder separa construcción de representación. Template Method separa esqueleto de detalles
DecoratorAñadir funcionalidad sin modificarDecorator usa composición para envolver. Template Method usa herencia para extender

La confusión más frecuente: Template Method vs Strategy. Ambos manejan variaciones de algoritmos, pero de forma opuesta:

Dicho en cristiano: si quieres variar algunos pasos de un proceso fijo, Template Method. Si quieres intercambiar todo el algoritmo, Strategy.

¿Cuál es la diferencia principal entre Template Method y Strategy?


⚖️ 7. Relación con SOLID

OCP (Open/Closed)

Puedes añadir nuevas variantes (nueva bebida, nuevo test) creando subclases sin modificar la clase base. El esqueleto no cambia.

DIP (Dependency Inversion)

El código cliente trabaja con la clase abstracta, no con las concretas. Puedes usar BebidaCaliente sin saber si es Cafe o Te.

LSP (Liskov Substitution)

Todas las subclases son sustituibles por la clase base. prepararReceta() funciona igual da igual si es Café o Té. El final lo garantiza.

SRP (Single Responsibility)

El flujo del algoritmo está en un solo sitio (la clase base). Los detalles de cada variante están en su propia subclase. Cada uno a lo suyo.


✅ 8. Ventajas y desventajas

Ventajas
  • Reutilización: El código común va a la clase base. Cero duplicación.
  • Control del flujo: El padre decide el orden. Las subclases no pueden romperlo (final).
  • Extensible: Nueva variante = nueva subclase. Sin tocar la clase base.
  • Hooks: Puntos de extensión opcionales. Las subclases extienden solo si quieren.
  • Hollywood Principle: La inversión de control hace que el framework te llame a ti, no al revés.
Desventajas
  • Rigidez: Las subclases están limitadas por el esqueleto. No puedes cambiar el orden de los pasos.
  • Herencia: Depende de herencia. Si ya heredas de otra clase, no puedes usar Template Method (PHP = herencia simple).
  • Clase base frágil: Si cambias la clase base, todas las subclases se ven afectadas.
  • Debug complejo: El flujo salta entre la clase base y la subclase. Hay que leer dos archivos para entender qué pasa.

⚠️ 9. Errores comunes

1. No declarar el Template Method como final

Si el método plantilla no es final, una subclase puede sobrescribirlo y romper todo el algoritmo:

// ❌ MAL: sin final, la subclase puede cambiar el flujo
abstract class BebidaCaliente
{
    public function prepararReceta(): void // Sin final 💀
    {
        $this->hervirAgua();
        $this->prepararIngrediente();
        $this->servirEnTaza();
    }
}

class CafeRebelde extends BebidaCaliente
{
    // Sobreescribe el template y cambia el orden
    public function prepararReceta(): void
    {
        $this->anadirCondimentos(); // ¡Primero los condimentos! 💀
        $this->servirEnTaza();
        // Ni hierve agua ni prepara ingrediente
    }
}

// ✅ BIEN: con final, nadie puede tocar el flujo
abstract class BebidaCaliente
{
    final public function prepararReceta(): void
    {
        $this->hervirAgua();
        $this->prepararIngrediente();
        $this->servirEnTaza();
    }
}

2. Demasiados métodos abstractos

Si tu clase base tiene 10 métodos abstractos, cada subclase tiene que implementar los 10. Eso es un horror de mantenimiento:

// ❌ MAL: 8 métodos abstractos
abstract class ExportadorDatos
{
    abstract protected function abrirArchivo(): void;
    abstract protected function escribirCabecera(): void;
    abstract protected function formatearFila(array $fila): string;
    abstract protected function escribirFila(string $fila): void;
    abstract protected function escribirPie(): void;
    abstract protected function cerrarArchivo(): void;
    abstract protected function validarDatos(): void;
    abstract protected function notificarCompletado(): void;
    // Cada subclase tiene que implementar 8 métodos... overkill
}

// ✅ BIEN: pocos abstractos, el resto hooks con valor por defecto
abstract class ExportadorDatos
{
    // Solo 2 abstractos (los que realmente cambian)
    abstract protected function formatearFila(array $fila): string;
    abstract protected function getExtension(): string;

    // Hooks con implementación por defecto
    protected function escribirCabecera(): void { /* vacío */ }
    protected function escribirPie(): void { /* vacío */ }

    // Métodos concretos (comunes)
    protected function abrirArchivo(): void { /* lógica común */ }
    protected function cerrarArchivo(): void { /* lógica común */ }
}

Si una subclase tiene que implementar un método pero el 80% de las veces no hace nada, debería ser un hook, no un método abstracto.

3. Lógica en la subclase que debería estar en la base

Si te encuentras duplicando código en todas las subclases, probablemente debería subir a la clase base:

// ❌ MAL: todas las subclases repiten lo mismo
class ExportadorCSV extends ExportadorDatos
{
    protected function exportar(): void
    {
        echo "Validando datos...\n"; // Duplicado en todas
        echo "Abriendo archivo...\n"; // Duplicado en todas
        // ... lógica específica de CSV
    }
}

class ExportadorPDF extends ExportadorDatos
{
    protected function exportar(): void
    {
        echo "Validando datos...\n"; // Lo mismo 💀
        echo "Abriendo archivo...\n"; // Lo mismo 💀
        // ... lógica específica de PDF
    }
}

// ✅ BIEN: lo común sube a la base
abstract class ExportadorDatos
{
    final public function exportar(): void
    {
        echo "Validando datos...\n";     // Una sola vez
        echo "Abriendo archivo...\n";     // Una sola vez
        $this->escribirContenido();       // Abstracto: cada uno a su manera
        echo "Archivo cerrado.\n";
    }

    abstract protected function escribirContenido(): void;
}

¿Qué problema tiene una clase base con demasiados métodos abstractos?

4. Usar Template Method cuando necesitas composición

Si cada variante necesita combinar comportamientos de forma flexible, la herencia se queda corta. Ahí es mejor Strategy:

// ❌ MAL: Template Method para algo que necesita composición
class PedidoConPayPalYEnvioExpress extends ProcesadorPedido { ... }
class PedidoConBizumYEnvioEstandar extends ProcesadorPedido { ... }
class PedidoConTarjetaYRecogidaTienda extends ProcesadorPedido { ... }
// Explosión de subclases: N pagos × M envíos = N*M clases 💀

// ✅ BIEN: usa Strategy (composición) para combinar
class ProcesadorPedido
{
    public function __construct(
        private EstrategiaPago $pago,
        private EstrategiaEnvio $envio,
    ) {}

    public function procesar(): void
    {
        $this->pago->pagar(...);
        $this->envio->enviar(...);
    }
}

🤔 10. ¿Cuándo usarlo y cuándo no?

Úsalo cuando...
  • Tienes varios procesos con la misma estructura pero pasos diferentes
  • Quieres reutilizar código común y evitar duplicación
  • Necesitas que el padre controle el flujo y las subclases solo rellenen huecos
  • Quieres puntos de extensión opcionales (hooks)
  • Estás creando un framework donde el usuario extiende clases base
No lo uses cuando...
  • Las variantes necesitan diferentes órdenes de pasos (el esqueleto no sirve)
  • Necesitas combinar comportamientos de forma flexible (usa Strategy/composición)
  • Ya heredas de otra clase (PHP solo permite herencia simple)
  • Solo tienes una variante (no necesitas abstracción)

Si te encuentras copiando y pegando un proceso que tiene 5 pasos donde 3 son iguales y 2 cambian, es Template Method pidiéndote que lo uses.

¿Cuándo NO deberías usar Template Method?


💡 11. Conclusión

El Patrón Template Method es la base de la herencia bien hecha. En vez de que las subclases decidan todo, la clase base define el esqueleto y las subclases solo rellenan los huecos. Es el Principio de Hollywood en acción: “No me llames, yo te llamo”.

Lo ves por todas partes: cada setUp()test()tearDown() en PHPUnit es Template Method. Cada ciclo de vida en Android o React es Template Method. Cada vez que un framework te pide que “sobreescribas este método”, alguien usó Template Method.

Su fuerza está en la reutilización y el control: código común en un solo sitio, flujo garantizado con final, y hooks para extensión opcional. Eso sí, cuidado con la herencia. Si necesitas combinar comportamientos de forma flexible (pago + envío + descuento), la composición (Strategy) es mejor opción.

EA, ¡saluditos y nos vemos en los bares! 🍻