El patrón Template Method define el esqueleto de un algoritmo en una operación, delegando a las subclases la implementación de ciertos pasos. La estructura general permanece inmutable, pero los puntos de extensión permiten variar detalles según las necesidades del dominio.
Es especialmente valioso cuando varios procesos comparten la misma secuencia de pasos pero difieren en cómo resuelven etapas específicas. La clase abstracta garantiza el flujo, mientras que las subclases rellenan los huecos (hooks) para adaptar el comportamiento.
En numerosos sistemas encontramos procedimientos estandarizados: abrir conexión, validar entrada, procesar datos, registrar resultados y cerrar recursos. Copiar esta secuencia en cada caso genera duplicación, dificulta el mantenimiento y abre la puerta a inconsistencias. Las variaciones suelen concentrarse en pasos concretos como la transformación de datos o la forma de persistirlos.
Template Method evita la duplicación al capturar el flujo común en una clase base, proporcionando puntos de extensión controlados para la lógica variable. Los consumidores heredan de la plantilla y completan los pasos obligatorios para personalizar el proceso.
La intención del patrón es definir el esqueleto de un algoritmo en una operación, diferiendo algunos pasos a subclases. Es pertinente cuando:
La motivación se centra en separar la invariancia del algoritmo de las partes que pueden cambiar, preservando el principio Abierto/Cerrado sin sacrificar claridad.
Los roles principales del patrón son:
La clase base puede contener métodos protegidos para asegurar que solo las subclases personalicen el comportamiento, conservando el contrato interno del proceso.
Las plantillas suelen distinguir dos tipos de pasos:
El uso cuidadoso de hooks evita la proliferación de subclases casi idénticas y reduce el riesgo de ruptura ante cambios menores.
Una empresa ofrece una plataforma que integra datos de clientes provenientes de distintas fuentes: CSV subidos manualmente, APIs externas y streams en tiempo real. Todos los flujos comparten pasos comunes: validar origen, normalizar datos, enriquecer campos, almacenar en el lago de datos y registrar auditoría. Lo que cambia es cómo se obtiene la fuente y qué enriquecimientos se aplican.
Template Method permite definir un proceso base de importación garantizando que todos los pasos se ejecuten en el orden correcto. Cada tipo de fuente hereda de la plantilla y proporciona sus detalles particulares, manteniendo una experiencia consistente para las operaciones y facilitando nuevas integraciones.
El siguiente ejemplo muestra una plantilla con pasos abstractos y hooks para registrar auditoría y personalizar la transformación de datos:
package tutorial.templatemethod;
import java.time.Instant;
import java.util.List;
import java.util.Map;
public abstract class ProcesoImportacion {
public final void ejecutar(String origen) {
registrarInicio(origen);
validarOrigen(origen);
List<Map<String, Object>> datosCrudos = obtenerDatos(origen);
List<Map<String, Object>> datosNormalizados = normalizar(datosCrudos);
List<Map<String, Object>> datosEnriquecidos = enriquecer(datosNormalizados);
persistir(datosEnriquecidos);
registrarFin(origen, datosEnriquecidos.size());
}
protected void registrarInicio(String origen) {
System.out.printf("[%s] Iniciando importación desde %s%n", Instant.now(), origen);
}
protected abstract void validarOrigen(String origen);
protected abstract List<Map<String, Object>> obtenerDatos(String origen);
protected List<Map<String, Object>> normalizar(List<Map<String, Object>> datos) {
return datos;
}
protected abstract List<Map<String, Object>> enriquecer(List<Map<String, Object>> datos);
protected abstract void persistir(List<Map<String, Object>> datos);
protected void registrarFin(String origen, int registros) {
System.out.printf("[%s] Importación finalizada: %d registros desde %s%n",
Instant.now(), registros, origen);
}
}
class ImportacionCsv extends ProcesoImportacion {
@Override
protected void validarOrigen(String origen) {
if (!origen.endsWith(".csv")) {
throw new IllegalArgumentException("El archivo debe ser CSV: " + origen);
}
}
@Override
protected List<Map<String, Object>> obtenerDatos(String origen) {
System.out.println("Leyendo archivo CSV " + origen);
return List.of(Map.of("cliente", "Ana", "pais", "AR"));
}
@Override
protected List<Map<String, Object>> enriquecer(List<Map<String, Object>> datos) {
System.out.println("Aplicando reglas de enriquecimiento para CSV");
return datos;
}
@Override
protected void persistir(List<Map<String, Object>> datos) {
System.out.println("Persistiendo datos en el lago de datos");
}
}
class ImportacionApi extends ProcesoImportacion {
@Override
protected void validarOrigen(String origen) {
if (!origen.startsWith("https://")) {
throw new IllegalArgumentException("La API debe ser segura: " + origen);
}
}
@Override
protected List<Map<String, Object>> obtenerDatos(String origen) {
System.out.println("Invocando endpoint remoto " + origen);
return List.of(Map.of("cliente", "Luis", "pais", "CL"));
}
@Override
protected List<Map<String, Object>> normalizar(List<Map<String, Object>> datos) {
System.out.println("Normalizando campos JSON a estructura interna");
return datos;
}
@Override
protected List<Map<String, Object>> enriquecer(List<Map<String, Object>> datos) {
System.out.println("Enriqueciendo datos con catálogo de países");
return datos;
}
@Override
protected void persistir(List<Map<String, Object>> datos) {
System.out.println("Encolando registros en el pipeline de streaming");
}
}
class AplicacionTemplateMethod {
public static void main(String[] args) {
ProcesoImportacion csv = new ImportacionCsv();
ProcesoImportacion api = new ImportacionApi();
csv.ejecutar("clientes-latam.csv");
api.ejecutar("https://api.ejemplo.com/clientes");
}
}
La clase ProcesoImportacion
fija el orden de las etapas: registro, validación, obtención, normalización, enriquecimiento y persistencia. Las subclases completan los métodos abstractos y, cuando lo necesitan, redefinen hooks como normalizar
para personalizar parte del flujo sin alterar el método plantilla. Cualquier nueva fuente que se agregue al ecosistema hereda de la plantilla y garantiza que se ejecuten los pasos críticos de auditoría.
Muchas APIs oficiales utilizan Template Method para compartir comportamiento. La documentación de AbstractList muestra cómo se define el esqueleto de operaciones de colecciones, delegando en las subclases detalles como el acceso a los elementos. Frameworks de persistencia, motores de plantillas y servidores web aplican la misma idea para encapsular flujos de peticiones, validaciones y renderizados.
Existen ajustes comunes del patrón:
La herencia excesiva puede derivar en jerarquías profundas y difíciles de mantener. También es peligroso sobrecargar la clase base con demasiados hooks, generando incertidumbre sobre cuáles deben sobrescribirse. Otro riesgo es romper la inmutabilidad del algoritmo permitiendo que las subclases alteren el orden de los pasos; la plantilla debe proteger esos puntos críticos.
final
para impedir que el orden se modifique en subclases.Template Method se complementa con Strategy para parametrizar pasos específicos sin recurrir a la herencia. Comparte objetivos con Builder cuando se construyen objetos complejos paso a paso, aunque Builder se centra en la creación y Template Method en la ejecución de procesos. También puede combinarse con Hook Method (nombre que a veces se da a los propios hooks) y con Chain of Responsibility cuando algunos pasos se delegan a filtros encadenados.