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 Té, el algoritmo es:
- Hervir agua.
- Poner la bolsita de té.
- Servir en taza.
- Añadir limón.
Para hacer Café, el algoritmo es:
- Hervir agua.
- Colar el café molido.
- Servir en taza.
- 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);
}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}]");
}
}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}");
}
}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();
}
}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 ---Ganchos (Hooks)
Un concepto clave en este patrón son los Hooks. Son métodos en la clase base que:
- Están vacíos o tienen una implementación por defecto mínima.
- 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ística | Template Method | Strategy |
|---|---|---|
| Mecanismo | Usa Herencia (Subclases). | Usa Composición (Interfaces). |
| Nivel | Modifica partes de un algoritmo. | Reemplaza el algoritmo entero. |
| Relación | Está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.