patron-template-method

Patrón Template Method: define el esqueleto

  • 5 min

El Template Method es un método que fija el esqueleto de un algoritmo y delega algunos pasos en subclases. Conserva el orden general mientras permite variar detalles concretos.

Imagina que estamos haciendo un programa para preparar bebidas calientes. Para hacer , el algoritmo es:

  1. Hervir agua.
  2. Poner la bolsita de té.
  3. Servir en taza.
  4. Añadir limón.

Para hacer Café, el algoritmo es:

  1. Hervir agua.
  2. Colar el café molido.
  3. Servir en taza.
  4. Añadir azúcar y leche.

Si os fijáis, el esqueleto es el mismo: Hervir -> Preparar -> Servir -> Condimentar. Solo cambian los pasos concretos 2 y 4.

Si copiamos y pegamos el código, tendremos duplicidad. Si mañana cambiamos la forma de hervir agua, tendremos que arreglarlo en dos sitios.

El patrón Template Method (Método Plantilla) nos permite definir la estructura de un algoritmo en una clase base y delegar la implementación de los pasos específicos a las subclases.

El Concepto: El Principio de Hollywood

Este patrón se basa en el Principio de Hollywood: “No nos llames, nosotros te llamaremos”.

La clase base (el padre) tiene el control. Ella tiene el método principal (la plantilla) que llama a los métodos de las subclases (los hijos) en el momento exacto. Los hijos no controlan el flujo, solo rellenan los huecos que el padre les deja.

Implementación en C#

Vamos a implementar un sistema de Procesamiento de Datos. Imagina una herramienta ETL que lee datos, los procesa y genera un reporte. Queremos soportar CSV y PDF.

La Clase Abstracta (La Plantilla)

Esta clase define el flujo. Fíjate en el método GenerarReporte. Es el método plantilla.

public abstract class GeneradorReportes
{
    // EL TEMPLATE METHOD
    // Define el esqueleto del algoritmo.
    // Normalmente NO es virtual, para evitar que los hijos rompan el flujo.
    public void GenerarReporte()
    {
        Console.WriteLine("\n--- Iniciando Generación de Reporte ---");
        
        ConectarBaseDeDatos(); // Paso común
        
        var datos = ExtraerDatos(); // Paso abstracto (los hijos deciden cómo)
        
        var datosProcesados = ProcesarDatos(datos); // Paso común (o hook)
        
        Exportar(datosProcesados); // Paso abstracto
        
        CerrarBaseDeDatos(); // Paso común
        
        Console.WriteLine("--- Reporte Finalizado ---");
    }

    // --- Pasos Comunes (Ya implementados) ---
    protected void ConectarBaseDeDatos()
    {
        Console.WriteLine("1. Conectando a la BBDD común...");
    }

    protected void CerrarBaseDeDatos()
    {
        Console.WriteLine("5. Cerrando conexión BBDD.");
    }

    // --- Pasos por defecto (Hooks) ---
    // Los hijos PUEDEN sobrescribirlo si quieren, pero no están obligados.
    protected virtual string ProcesarDatos(string datos)
    {
        Console.WriteLine("3. Procesando datos (lógica base)...");
        return datos.ToUpper(); // Por defecto, los ponemos en mayúsculas
    }

    // --- Pasos Abstractos (Obligatorios) ---
    // Los hijos DEBEN implementarlos.
    protected abstract string ExtraerDatos();
    protected abstract void Exportar(string datos);
}
Copied!

Las Implementaciones Concretas

Ahora creamos los “Sabores” de nuestro algoritmo.

Reporte CSV:

public class ReporteCSV : GeneradorReportes
{
    protected override string ExtraerDatos()
    {
        Console.WriteLine("2. Extrayendo datos para CSV (rápido)...");
        return "datos,csv,ejemplo";
    }

    protected override void Exportar(string datos)
    {
        Console.WriteLine($"4. Guardando archivo .csv con contenido: [{datos}]");
    }
}
Copied!

Reporte PDF:

public class ReportePDF : GeneradorReportes
{
    protected override string ExtraerDatos()
    {
        Console.WriteLine("2. Extrayendo datos para PDF (incluye imágenes)...");
        return "<pdf>datos visuales</pdf>";
    }

    // Aquí decidimos sobrescribir el Hook también
    protected override string ProcesarDatos(string datos)
    {
        Console.WriteLine("3. Procesando datos para PDF (añadiendo estilos)...");
        return $"** {datos} **";
    }

    protected override void Exportar(string datos)
    {
        Console.WriteLine($"4. Renderizando documento .pdf con contenido: {datos}");
    }
}
Copied!

El Cliente

Al cliente no le importa el flujo interno, solo llama al método público.

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(">>> Cliente pide reporte CSV:");
        var csv = new ReporteCSV();
        csv.GenerarReporte(); // El padre orquesta, el hijo trabaja

        Console.WriteLine("\n>>> Cliente pide reporte PDF:");
        var pdf = new ReportePDF();
        pdf.GenerarReporte();
    }
}
Copied!

Salida:

>>> Cliente pide reporte CSV:

--- Iniciando Generación de Reporte ---
1. Conectando a la BBDD común...
2. Extrayendo datos para CSV (rápido)...
3. Procesando datos (lógica base)...
4. Guardando archivo .csv con contenido: [DATOS,CSV,EJEMPLO]
5. Cerrando conexión BBDD.
--- Reporte Finalizado ---

>>> Cliente pide reporte PDF:

--- Iniciando Generación de Reporte ---
1. Conectando a la BBDD común...
2. Extrayendo datos para PDF (incluye imágenes)...
3. Procesando datos para PDF (añadiendo estilos)...
4. Renderizando documento .pdf con contenido: ** <pdf>datos visuales</pdf> **
5. Cerrando conexión BBDD.
--- Reporte Finalizado ---
Copied!

Ganchos (Hooks)

Un concepto clave en este patrón son los Hooks. Son métodos en la clase base que:

  1. Están vacíos o tienen una implementación por defecto mínima.
  2. No son abstractos.

Sirven para dar puntos de extensión opcionales.

  • Ejemplo: virtual void OnReportGenerated() { }
  • La clase base lo llama al final. Si la subclase quiere hacer un log o enviar un email, sobrescribe el método. Si no, no pasa nada.

Template Method vs Strategy

Es la duda eterna, porque ambos “personalizan algoritmos”.

CaracterísticaTemplate MethodStrategy
MecanismoUsa Herencia (Subclases).Usa Composición (Interfaces).
NivelModifica partes de un algoritmo.Reemplaza el algoritmo entero.
RelaciónEstática (definida al compilar).Dinámica (intercambiable en ejecución).
  • Usa Template Method cuando tengas un algoritmo fijo y solo quieras variar algunos pasos concretos.
  • Usa Strategy cuando tengas varios algoritmos totalmente distintos para hacer lo mismo y quieras cambiarlos sobre la marcha.

Template Method aparece con frecuencia en frameworks. Cuando sobrescribes métodos de ciclo de vida, el framework mantiene el flujo general y tu código personaliza ciertos puntos de extensión.