Programación · Patrones de diseño
Patrón Template Method explicado con ejemplos
Respuesta corta
Template Method es un patrón de diseño de comportamiento que define el esqueleto de un algoritmo en un método de una clase base y permite que las subclases sobrescriban pasos concretos sin cambiar el orden general. La clase base decide qué ocurre y cuándo; las subclases deciden cómo se hacen algunos de los pasos. Los pasos opcionales con una implementación por defecto se llaman hooks.
Intención y el problema que resuelve
Intención: definir el esqueleto de un algoritmo en una operación, delegando algunos pasos a las subclases. Template Method permite que las subclases redefinan ciertos pasos de un algoritmo sin cambiar su estructura. Es uno de los patrones de comportamiento del libro de la Banda de los Cuatro (Gang of Four, GoF).
Imagina una herramienta de informes que exporta datos como CSV y como JSON. Ambos exportadores hacen lo mismo en el mismo orden: cargar las filas, comprobar que hay alguna, darles formato y añadir un pie con el número de filas. Solo difiere el formato. Si cada exportador se escribe por separado, los pasos compartidos se copian, y una corrección en uno (por ejemplo, un mejor manejo de un resultado vacío) se olvida fácilmente en el otro.
Con Template Method, una clase base Exporter contiene el método export con la secuencia fija de pasos. Los pasos que varían son métodos abstractos. CsvExporter y JsonExporter implementan solo esos.
Estructura y participantes
| Rol | Responsabilidad |
|---|---|
| Clase abstracta | Implementa el método plantilla, que llama a los pasos en un orden fijo; declara pasos abstractos y puede ofrecer hooks |
| Método plantilla | El método público que define el algoritmo; normalmente no está pensado para sobrescribirse |
| Operación primitiva | Un paso abstracto que toda subclase debe implementar |
| Hook | Un paso con una implementación por defecto (a menudo vacía) que las subclases pueden sobrescribir |
| Clase concreta | Implementa las operaciones primitivas y sobrescribe los hooks según haga falta |
Este diseño invierte el control: la clase base llama a los métodos de la subclase, y no al revés. A veces se llama el principio de Hollywood: "no nos llames, nosotros te llamaremos".
Implementación en TypeScript
export es el método plantilla. format es una operación primitiva que las subclases deben implementar, y header es un hook con un valor por defecto vacío.
type Row = { name: string; score: number };
abstract class Exporter {
// The template method: fixed order of steps
export(rows: Row[]): string {
if (rows.length === 0) return "no data";
const lines = [this.header(), this.format(rows), "rows: " + rows.length];
return lines.filter((line) => line !== "").join("\n");
}
protected abstract format(rows: Row[]): string; // required step
protected header(): string { return ""; } // hook: optional step
}
class CsvExporter extends Exporter {
protected header() { return "name,score"; }
protected format(rows: Row[]) {
return rows.map((r) => r.name + "," + r.score).join("\n");
}
}
class JsonExporter extends Exporter {
protected format(rows: Row[]) {
return JSON.stringify(rows);
}
}
const rows = [{ name: "Ana", score: 9 }, { name: "Ben", score: 7 }];
console.log(new CsvExporter().export(rows));
// name,score
// Ana,9
// Ben,7
// rows: 2
console.log(new JsonExporter().export(rows));
// [{"name":"Ana","score":9},{"name":"Ben","score":7}]
// rows: 2
console.log(new JsonExporter().export([])); // no dataLa comprobación de datos vacíos y el pie viven en un solo lugar. Un nuevo exportador XML implementaría format y obtendría el resto gratis.
La misma idea con composición
La herencia no es la única vía. Puedes pasar los pasos variables como funciones, lo que convierte el patrón en un pariente cercano de Strategy y evita las jerarquías de clases:
type Row = { name: string; score: number };
type Steps = { format: (rows: Row[]) => string; header?: () => string };
function exportRows(rows: Row[], steps: Steps): string {
if (rows.length === 0) return "no data";
const lines = [steps.header?.() ?? "", steps.format(rows), "rows: " + rows.length];
return lines.filter((line) => line !== "").join("\n");
}
console.log(exportRows([{ name: "Ana", score: 9 }], { format: (r) => r[0].name }));
// Ana
// rows: 1Prefiere esta forma cuando los pasos son pequeños y no necesitas estado compartido en una clase base, o cuando un exportador debe combinar pasos de varias fuentes.
Cuándo usarlo y cuándo no
- Úsalo cuando varias clases ejecutan el mismo algoritmo y solo difieren unos pocos pasos, y quieres eliminar las partes duplicadas.
- Úsalo en frameworks que deben controlar el orden de las operaciones mientras permiten a los usuarios personalizar pasos, como la preparación y la limpieza en las pruebas.
- Usa hooks para ofrecer puntos de extensión opcionales sin obligar a cada subclase a implementarlos.
- No lo uses cuando las variaciones son grandes o deben cambiar en tiempo de ejecución. Strategy, con composición, es más flexible.
- Evita las jerarquías profundas de métodos plantilla. Cuando las subclases sobrescriben pasos de subclases de subclases, el flujo se vuelve difícil de seguir.
Usos en el mundo real
AbstractListde Java: implementasget(int)ysize(), y la clase base ofrece la iteración,indexOf,containsy otros métodos construidos sobre ellos.InputStreamde Java: las subclases implementanread()de un solo byte, y la clase base implementaread(byte[], int, int)llamándolo (las subclases pueden sobrescribirlo para ganar velocidad).- Frameworks de pruebas: JUnit,
unittestde Python y otros llaman asetUp, al método de prueba y atearDownen un orden fijo, y tú sobrescribes los pasos que necesitas. - Los frameworks de interfaz con métodos de ciclo de vida, como
componentDidMounten los componentes de clase de React, llaman a tus métodos en puntos fijos de su propio algoritmo.
Patrones relacionados
| Patrón | En qué se diferencia de Template Method |
|---|---|
| Strategy | Strategy reemplaza un algoritmo completo por composición en tiempo de ejecución; Template Method fija el algoritmo y varía partes de él mediante herencia en tiempo de compilación. |
| Factory Method | Un método de fábrica suele ser uno de los pasos de un método plantilla: el paso que crea un objeto. |
| Funciones hook y callbacks | Pasar callbacks a un algoritmo fijo es la versión basada en composición de la misma idea. |
Preguntas frecuentes
¿Qué es un hook en el patrón Template Method?
Un hook es un paso que tiene una implementación por defecto en la clase base, a menudo sin hacer nada. Las subclases pueden sobrescribirlo para añadir comportamiento en ese punto, pero no están obligadas.
¿Cuál es la diferencia entre Template Method y Strategy?
Template Method usa herencia: la clase base posee el algoritmo y las subclases sobrescriben pasos. Strategy usa composición: el contexto delega el algoritmo completo en un objeto o función que puede intercambiarse en tiempo de ejecución.
¿Debería poder sobrescribirse el propio método plantilla?
No. Su propósito es fijar el orden de los pasos. En Java o C# puedes marcarlo como final o no virtual; en TypeScript tienes que documentarlo, porque no existe la palabra clave final.
¿Cómo se relaciona Template Method con Factory Method?
Factory Method suele ser una especialización de Template Method: el algoritmo de la clase base incluye un paso que crea un objeto, y las subclases deciden qué clase crear.