Patrón Template Method
Un método de la clase base fija los pasos de un algoritmo y su orden, y las subclases rellenan solo los que cambian. Así funcionan los servlets, los tests de JUnit o el ciclo de vida de Android.
nivel básicoTambién: método plantilla, plantilla, hook, gancho
Visualízalo paso a paso
Cambia los datos, dale a reproducir y sigue cada paso en el dibujo, en la línea de Java que se ejecuta y en sus variables.
Template Method
Elige el tipo de viaje y el destino. Los pasos fijos están en la clase base; los que cambian, en la subclase.
Paso 1
new AltaVelocidad().hacer("Sevilla"): el método plantilla está en Viaje y es final. Columna izquierda: pasos de la clase base; derecha: lo que pone AltaVelocidad.
1final void hacer(String destino) { // viaje = AltaVelocidad, paso = 0
2 comprarBillete();
3 if (necesitaControl())
4 System.out.println(" Pasar el control");
5 System.out.println(" Subir al tren");
6 trayecto(destino);
7 System.out.println(" Bajar en " + destino);
8}
9
10protected boolean necesitaControl() { return false; }Variables
- viaje
- AltaVelocidad
- paso
- 0
Atajos con el foco dentro del visualizador: ← → paso a paso, Espacio reproducir o pausar, Inicio/Fin ir al principio o al final.
La idea
Dos viajes en tren siguen los mismos pasos: comprar el billete, subir, hacer el trayecto y bajar. Lo que cambia es cómo se compra el billete o cuánto dura el trayecto. Si cada clase escribe el viaje entero, el orden de los pasos se copia y, cuando hay que añadir uno (pasar un control), hay que tocar todas.
El patrón Template Method escribe el algoritmo una sola vez en la clase base, en un método (el método plantilla, hacer) que llama a los pasos en su orden. Los pasos que cambian son métodos abstractos que escribe cada subclase; los opcionales, «ganchos» con un comportamiento por defecto que la subclase puede redefinir (necesitaControl() devuelve false salvo en el AVE).
El método plantilla suele ser final: las subclases deciden el contenido de los pasos, nunca su orden. Es el principio de Hollywood, «no nos llames, nosotros te llamamos»: el framework llama a tu doGet, a tu onCreate o a tu método @Test cuando le toca.
Cuándo usarlo
- Varias clases hacen lo mismo con pequeñas variaciones en algunos pasos: el esqueleto se escribe una vez.
- Quieres controlar el orden de los pasos y que las subclases solo puedan cambiar algunos (por ejemplo, para garantizar que siempre se cierra un recurso).
- Estás escribiendo un framework o una clase base para que otros la extiendan (servlets, tests, actividades, juegos).
Cuándo no
- Si los pasos que cambian se pueden pasar como objetos o lambdas: con composición (Strategy) no hace falta heredar.
- Si las subclases necesitan cambiar el orden de los pasos: el esqueleto no es común de verdad.
Participantes
- Clase abstracta. Tiene el método plantilla (
final), que llama a los pasos en orden, y declara los pasos abstractos y los ganchos. - Pasos abstractos. Los que cada subclase tiene que escribir (
comprarBillete,trayecto). - Ganchos. Pasos opcionales con un comportamiento por defecto (
necesitaControl()devuelvefalse). - Clases concretas. Cada subclase (
Cercanias,AltaVelocidad) rellena los pasos, sin tocar el orden.
Diagrama de clases
Ver el diagrama en PlantUML
1@startuml
2abstract class Viaje {
3 +hacer(destino : String) : void
4 #{abstract} comprarBillete() : void
5 #{abstract} trayecto(destino : String) : void
6 #necesitaControl() : boolean
7}
8class Cercanias {
9 #comprarBillete() : void
10 #trayecto(destino : String) : void
11}
12class AltaVelocidad {
13 #comprarBillete() : void
14 #trayecto(destino : String) : void
15 #necesitaControl() : boolean
16}
17Viaje <|-- Cercanias
18Viaje <|-- AltaVelocidad
19@endumlPuedes copiarlo en el editor de diagramas UML y modificarlo.
El código
Dos formas de viajar en tren
hacer está escrito una vez y es final. Cada viaje rellena dos pasos, y el AVE además cambia un gancho para pasar el control de equipajes.
1/** La clase base fija el esqueleto del viaje; las subclases rellenan los pasos que cambian. */
2abstract class Viaje {
3 /** El método plantilla: el orden de los pasos es siempre el mismo y no se puede cambiar (final). */
4 final void hacer(String destino) {
5 System.out.println("Viaje a " + destino + ":");
6 comprarBillete();
7 if (necesitaControl()) System.out.println(" Pasar el control de equipajes"); // un gancho
8 System.out.println(" Subir al tren");
9 trayecto(destino);
10 System.out.println(" Bajar en " + destino);
11 }
12
13 protected abstract void comprarBillete(); // pasos que escribe cada subclase
14 protected abstract void trayecto(String destino);
15 protected boolean necesitaControl() { return false; } // gancho: tiene un valor por defecto
16}
17
18class Cercanias extends Viaje {
19 protected void comprarBillete() { System.out.println(" Pasar el abono por el torno"); }
20 protected void trayecto(String destino) { System.out.println(" 12 paradas de pie hasta " + destino); }
21}
22
23class AltaVelocidad extends Viaje {
24 protected void comprarBillete() { System.out.println(" Comprar un billete con asiento: coche 5, plaza 8A"); }
25 protected boolean necesitaControl() { return true; }
26 protected void trayecto(String destino) { System.out.println(" 2 h 30 min sin paradas hasta " + destino); }
27}
28
29public class Main {
30 public static void main(String[] args) {
31 new Cercanias().hacer("Alcalá");
32 new AltaVelocidad().hacer("Sevilla");
33 }
34}from abc import ABC, abstractmethod
class Viaje(ABC):
"""La clase base fija el esqueleto del viaje; las subclases rellenan los pasos que cambian."""
def hacer(self, destino):
"""El método plantilla: el orden de los pasos es siempre el mismo (Python no tiene final)."""
print(f"Viaje a {destino}:")
self.comprar_billete()
if self.necesita_control(): # un gancho
print(" Pasar el control de equipajes")
print(" Subir al tren")
self.trayecto(destino)
print(f" Bajar en {destino}")
@abstractmethod
def comprar_billete(self): ... # pasos que escribe cada subclase
@abstractmethod
def trayecto(self, destino): ...
def necesita_control(self): # gancho: tiene un valor por defecto
return False
class Cercanias(Viaje):
def comprar_billete(self):
print(" Pasar el abono por el torno")
def trayecto(self, destino):
print(f" 12 paradas de pie hasta {destino}")
class AltaVelocidad(Viaje):
def comprar_billete(self):
print(" Comprar un billete con asiento: coche 5, plaza 8A")
def necesita_control(self):
return True
def trayecto(self, destino):
print(f" 2 h 30 min sin paradas hasta {destino}")
Cercanias().hacer("Alcalá")
AltaVelocidad().hacer("Sevilla")/** La clase base fija el esqueleto del viaje; las subclases rellenan los pasos que cambian. */
class Viaje {
/** El método plantilla: el orden de los pasos es siempre el mismo. */
hacer(destino) {
console.log(`Viaje a ${destino}:`);
this.comprarBillete();
if (this.necesitaControl()) console.log(" Pasar el control de equipajes"); // un gancho
console.log(" Subir al tren");
this.trayecto(destino);
console.log(` Bajar en ${destino}`);
}
comprarBillete() { throw new Error("Cada viaje compra su billete"); } // pasos que escribe cada subclase
trayecto(destino) { throw new Error("Cada viaje hace su trayecto"); }
necesitaControl() { return false; } // gancho: valor por defecto
}
class Cercanias extends Viaje {
comprarBillete() { console.log(" Pasar el abono por el torno"); }
trayecto(destino) { console.log(` 12 paradas de pie hasta ${destino}`); }
}
class AltaVelocidad extends Viaje {
comprarBillete() { console.log(" Comprar un billete con asiento: coche 5, plaza 8A"); }
necesitaControl() { return true; }
trayecto(destino) { console.log(` 2 h 30 min sin paradas hasta ${destino}`); }
}
new Cercanias().hacer("Alcalá");
new AltaVelocidad().hacer("Sevilla");using System;
// La clase base fija el esqueleto del viaje; las subclases rellenan los pasos que cambian.
abstract class Viaje {
// El método plantilla: no es virtual, así que ninguna subclase puede cambiar el orden.
public void Hacer(string destino) {
Console.WriteLine(quot;Viaje a {destino}:");
ComprarBillete();
if (NecesitaControl()) Console.WriteLine(" Pasar el control de equipajes"); // un gancho
Console.WriteLine(" Subir al tren");
Trayecto(destino);
Console.WriteLine(quot; Bajar en {destino}");
}
protected abstract void ComprarBillete(); // pasos que escribe cada subclase
protected abstract void Trayecto(string destino);
protected virtual bool NecesitaControl() => false; // gancho: tiene un valor por defecto
}
class Cercanias : Viaje {
protected override void ComprarBillete() => Console.WriteLine(" Pasar el abono por el torno");
protected override void Trayecto(string destino) => Console.WriteLine(quot; 12 paradas de pie hasta {destino}");
}
class AltaVelocidad : Viaje {
protected override void ComprarBillete() => Console.WriteLine(" Comprar un billete con asiento: coche 5, plaza 8A");
protected override bool NecesitaControl() => true;
protected override void Trayecto(string destino) => Console.WriteLine(quot; 2 h 30 min sin paradas hasta {destino}");
}
class Program {
static void Main() {
new Cercanias().Hacer("Alcalá");
new AltaVelocidad().Hacer("Sevilla");
}
}<?php
/** La clase base fija el esqueleto del viaje; las subclases rellenan los pasos que cambian. */
abstract class Viaje {
/** El método plantilla: final, así que ninguna subclase puede cambiar el orden. */
final public function hacer(string $destino): void {
echo "Viaje a $destino:\n";
$this->comprarBillete();
if ($this->necesitaControl()) echo " Pasar el control de equipajes\n"; // un gancho
echo " Subir al tren\n";
$this->trayecto($destino);
echo " Bajar en $destino\n";
}
abstract protected function comprarBillete(): void; // pasos que escribe cada subclase
abstract protected function trayecto(string $destino): void;
protected function necesitaControl(): bool { return false; } // gancho: valor por defecto
}
class Cercanias extends Viaje {
protected function comprarBillete(): void { echo " Pasar el abono por el torno\n"; }
protected function trayecto(string $destino): void { echo " 12 paradas de pie hasta $destino\n"; }
}
class AltaVelocidad extends Viaje {
protected function comprarBillete(): void { echo " Comprar un billete con asiento: coche 5, plaza 8A\n"; }
protected function necesitaControl(): bool { return true; }
protected function trayecto(string $destino): void { echo " 2 h 30 min sin paradas hasta $destino\n"; }
}
(new Cercanias())->hacer("Alcalá");
(new AltaVelocidad())->hacer("Sevilla");Salida al ejecutarlo (la misma en los 5 lenguajes)
Viaje a Alcalá: Pasar el abono por el torno Subir al tren 12 paradas de pie hasta Alcalá Bajar en Alcalá Viaje a Sevilla: Comprar un billete con asiento: coche 5, plaza 8A Pasar el control de equipajes Subir al tren 2 h 30 min sin paradas hasta Sevilla Bajar en Sevilla
Servlets, AbstractList y frameworks
Métodos plantilla que ya usas sin saberlo.
1// HttpServlet: service() es el método plantilla; tú solo escribes doGet o doPost
2@WebServlet("/alumnos")
3public class AlumnosServlet extends HttpServlet {
4 @Override
5 protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
6 resp.getWriter().println("Listado de alumnos");
7 }
8}
9
10// AbstractList: escribes get y size, y te da iterator, contains, indexOf, equals, toString…
11class Pares extends AbstractList<Integer> {
12 public Integer get(int i) { return 2 * i; }
13 public int size() { return 10; }
14}
15
16// JUnit y Android siguen la misma idea: el framework fija el orden (@BeforeEach, @Test,
17// @AfterEach; onCreate, onStart, onResume…) y tú rellenas los pasos que necesitas.import unittest
# unittest es un método plantilla: el framework llama a setUp, al test y a tearDown en su orden
class PruebasAgenda(unittest.TestCase):
def setUp(self):
self.agenda = Agenda()
def test_anadir(self):
self.agenda.anadir("Ana")
self.assertEqual(self.agenda.total(), 1)
def tearDown(self):
self.agenda.cerrar()
# Django hace lo mismo con sus vistas: View.dispatch() llama a tu get() o post()// Los componentes de clase de React siguen un esqueleto fijo: React llama a render y a los
// métodos del ciclo de vida (componentDidMount, componentWillUnmount…) cuando le toca
class Reloj extends React.Component {
componentDidMount() { this.temporizador = setInterval(() => this.forceUpdate(), 1000); }
componentWillUnmount() { clearInterval(this.temporizador); }
render() { return <p>{new Date().toLocaleTimeString()}</p>; }
}
// Los custom elements también: el navegador llama a connectedCallback al insertarlos
class MiBoton extends HTMLElement {
connectedCallback() { this.textContent = "Púlsame"; }
}// Stream es un método plantilla: escribes Read y Write, y te da CopyTo, ReadAsync, etc.
// En ASP.NET Core, un BackgroundService ejecuta tu ExecuteAsync dentro de su ciclo de vida
public class Recordatorios : BackgroundService {
protected override async Task ExecuteAsync(CancellationToken cancelar) {
while (!cancelar.IsCancellationRequested) {
await EnviarRecordatorios();
await Task.Delay(TimeSpan.FromHours(1), cancelar);
}
}
}<?php
// PHPUnit: el framework llama a setUp, a cada test y a tearDown en su orden
class AgendaTest extends PHPUnit\Framework\TestCase {
private Agenda $agenda;
protected function setUp(): void { $this->agenda = new Agenda(); }
public function testAnadir(): void {
$this->agenda->anadir("Ana");
$this->assertSame(1, $this->agenda->total());
}
}
// Los comandos de Laravel también: el framework prepara todo y llama a tu handle()En la práctica
HttpServlet.service()llama adoGet,doPost…;AbstractList,AbstractMapeInputStreampiden unos pocos métodos y dan el resto hecho.- Los ciclos de vida de Android (
onCreate,onStart,onResume…) y de los componentes de los frameworks web. - JUnit: el framework ejecuta
@BeforeEach, el test y@AfterEachen su orden; en PHPUnit,setUpytearDown. - Los juegos por turnos, las importaciones de datos (leer, validar, guardar, informar) y los informes con cabecera, filas y pie.
Errores típicos
- No marcar como
finalel método plantilla: una subclase puede redefinirlo y saltarse pasos o cambiar su orden. - Hacer abstractos todos los pasos, incluso los que casi todas las subclases hacen igual: mejor un gancho con un valor por defecto.
- Que las subclases llamen ellas mismas a los pasos en vez de dejar que los llame el método plantilla.
- Usar herencia solo para reutilizar dos líneas: si lo que cambia es un algoritmo entero, Strategy suele encajar mejor.
Ejercicios
Cada ejercicio se corrige solo con sus pruebas (algunas ocultas). Escribe tu solución en el editor y pulsa Ejecutar o Comprobar; la solución explicada está debajo, por si te atascas.
1. Informes con el mismo esqueleto
Informe.generar escribe siempre una cabecera, una fila por cada alumno incluido y un pie. Ya está el informe de texto. Escribe InformeAprobados (que usa el gancho incluir para quedarse solo con las notas de 5 o más) e InformeCsv, y regístralos en el main con las claves aprobados y csv.
- Órdenes:
alumno nombre notaeinforme texto|aprobados|csv. Las notas se escriben con coma y sin ceros de más. - Aprobados:
APROBADOS, una línea+ nombre (nota)por aprobado yN aprobados, media M(1 aprobado…), oNadie ha aprobado. - CSV:
nombre;notay una líneanombre;notapor alumno, sin pie.
Ejemplo
alumno Ana 8.5 alumno Luis 4 alumno Marta 6.25 informe texto informe aprobados informe csv
NOTAS Ana 8,5 Luis 4 Marta 6,25 Media de 3 alumnos: 6,25 APROBADOS + Ana (8,5) + Marta (6,25) 2 aprobados, media 7,38 nombre;nota Ana;8,5 Luis;4 Marta;6,25
Ver la solución explicada
1import java.math.*;
2import java.util.*;
3
4record Alumno(String nombre, double nota) {}
5
6/** El esqueleto de cualquier informe: cabecera, una fila por alumno incluido y pie. */
7abstract class Informe {
8 final String generar(List<Alumno> alumnos) {
9 StringBuilder sb = new StringBuilder(cabecera());
10 int n = 0;
11 double suma = 0;
12 for (Alumno a : alumnos) {
13 if (!incluir(a)) continue;
14 sb.append(fila(a));
15 n++;
16 suma += a.nota();
17 }
18 sb.append(pie(n, n == 0 ? 0 : suma / n));
19 return sb.toString();
20 }
21
22 protected abstract String cabecera();
23 protected abstract String fila(Alumno a);
24 protected abstract String pie(int n, double media);
25 protected boolean incluir(Alumno a) { return true; } // gancho: por defecto, todos
26}
27
28class InformeTexto extends Informe {
29 protected String cabecera() { return "NOTAS\n"; }
30 protected String fila(Alumno a) { return String.format(" %-10s %s", a.nombre(), Main.nota(a.nota())) + "\n"; }
31 protected String pie(int n, double media) { return "Media de " + n + " alumnos: " + Main.nota(media) + "\n"; }
32}
33
34class InformeAprobados extends Informe {
35 protected boolean incluir(Alumno a) { return a.nota() >= 5; }
36 protected String cabecera() { return "APROBADOS\n"; }
37 protected String fila(Alumno a) { return " + " + a.nombre() + " (" + Main.nota(a.nota()) + ")\n"; }
38 protected String pie(int n, double media) {
39 return n == 0 ? "Nadie ha aprobado\n" : n + (n == 1 ? " aprobado" : " aprobados") + ", media " + Main.nota(media) + "\n";
40 }
41}
42
43class InformeCsv extends Informe {
44 protected String cabecera() { return "nombre;nota\n"; }
45 protected String fila(Alumno a) { return a.nombre() + ";" + Main.nota(a.nota()) + "\n"; }
46 protected String pie(int n, double media) { return ""; }
47}
48
49public class Main {
50 static String nota(double x) {
51 return BigDecimal.valueOf(x).setScale(2, RoundingMode.HALF_UP).stripTrailingZeros().toPlainString().replace('.', ',');
52 }
53
54 public static void main(String[] args) {
55 Map<String, Informe> informes = new HashMap<>();
56 informes.put("texto", new InformeTexto());
57 informes.put("aprobados", new InformeAprobados());
58 informes.put("csv", new InformeCsv());
59 List<Alumno> alumnos = new ArrayList<>();
60 Scanner sc = new Scanner(System.in);
61 while (sc.hasNextLine()) {
62 String[] p = sc.nextLine().trim().split("\\s+");
63 if (p[0].isEmpty()) continue;
64 try {
65 if (p[0].equals("alumno") && p.length == 3) {
66 double n = Double.parseDouble(p[2]);
67 if (n < 0 || n > 10) throw new NumberFormatException();
68 alumnos.add(new Alumno(p[1], n));
69 } else if (p[0].equals("informe") && p.length == 2) {
70 Informe i = informes.get(p[1]);
71 System.out.print(i == null ? "Informe desconocido: " + p[1] + "\n" : i.generar(alumnos));
72 } else {
73 System.out.println("Orden no válida: " + String.join(" ", p));
74 }
75 } catch (NumberFormatException e) {
76 System.out.println("Nota no válida: " + String.join(" ", p));
77 }
78 }
79 }
80}El recorrido, el filtrado y la media están escritos una sola vez; cada informe solo dice cómo se escribe.
El gancho permite cambiar un paso opcional sin obligar a las demás subclases a escribirlo.
2. Juegos por turnos
Juego.jugar es el bucle de cualquier juego por turnos: pide jugadas, las valida, cambia de turno y anuncia al ganador. Ya está el Nim. Escribe Carrera (la carrera a 21) rellenando los cinco pasos abstractos, y regístrala en el main con juego carrera.
- La primera línea es
juego nimojuego carrera; después, una jugada por línea. Juegan Ana y Luis, empieza Ana. - Carrera a 21: se empieza en 0 y cada jugador suma 1, 2 o 3, sin repetir lo que acaba de sumar el rival y sin pasarse de 21; gana quien llega justo a 21.
nombre()esCarrera a 21,estado()esvan Ny cada jugada válida se cuenta comoJugador suma n: van N. - Una jugada no válida devuelve
nullenmover, y el bucle hace repetir al mismo jugador.
Ejemplo
juego carrera 3 2 3 1 3 2 3 1 3
Empieza Carrera a 21: van 0 Ana suma 3: van 3 Luis suma 2: van 5 Ana suma 3: van 8 Luis suma 1: van 9 Ana suma 3: van 12 Luis suma 2: van 14 Ana suma 3: van 17 Luis suma 1: van 18 Ana suma 3: van 21 Gana Ana
Ver la solución explicada
1import java.util.*;
2
3/** El esqueleto de cualquier juego por turnos entre dos jugadores. */
4abstract class Juego {
5 protected final String[] jugadores = {"Ana", "Luis"};
6 protected int turno = 0;
7
8 /** El método plantilla: el bucle de turnos es el mismo para todos los juegos. */
9 final void jugar(Iterator<String> jugadas) {
10 System.out.println("Empieza " + nombre() + ": " + estado());
11 while (!terminado()) {
12 if (!jugadas.hasNext()) {
13 System.out.println("Partida sin terminar");
14 return;
15 }
16 String jugador = jugadores[turno];
17 String jugada = jugadas.next();
18 String r = mover(jugador, jugada);
19 if (r == null) {
20 System.out.println(" Jugada no válida de " + jugador + ": " + jugada);
21 continue; // vuelve a tirar el mismo jugador
22 }
23 System.out.println(" " + r);
24 if (!terminado()) turno = 1 - turno;
25 }
26 System.out.println("Gana " + ganador());
27 }
28
29 protected abstract String nombre();
30 protected abstract String estado();
31 /** Aplica la jugada y cuenta lo que ha pasado, o devuelve null si no es válida. */
32 protected abstract String mover(String jugador, String jugada);
33 protected abstract boolean terminado();
34 protected abstract String ganador();
35
36 /** La jugada como número, o -1 si no lo es. */
37 static int numero(String jugada) {
38 try { return Integer.parseInt(jugada); } catch (NumberFormatException e) { return -1; }
39 }
40}
41
42/** Nim: hay 15 palillos y cada jugador quita 1, 2 o 3; quien quita el último pierde. */
43class Nim extends Juego {
44 private int quedan = 15;
45 protected String nombre() { return "Nim"; }
46 protected String estado() { return quedan + " palillos"; }
47 protected String mover(String jugador, String jugada) {
48 int n = numero(jugada);
49 if (n < 1 || n > 3 || n > quedan) return null;
50 quedan -= n;
51 return jugador + " quita " + n + ": quedan " + quedan;
52 }
53 protected boolean terminado() { return quedan == 0; }
54 protected String ganador() { return jugadores[1 - turno]; } // quien quitó el último pierde
55}
56
57/** Carrera a 21: cada jugador suma 1, 2 o 3 (sin repetir lo que acaba de sumar el rival); gana quien llega a 21. */
58class Carrera extends Juego {
59 private int total = 0;
60 private int anterior = 0;
61 protected String nombre() { return "Carrera a 21"; }
62 protected String estado() { return "van " + total; }
63 protected String mover(String jugador, String jugada) {
64 int n = numero(jugada);
65 if (n < 1 || n > 3 || n == anterior || total + n > 21) return null;
66 total += n;
67 anterior = n;
68 return jugador + " suma " + n + ": van " + total;
69 }
70 protected boolean terminado() { return total == 21; }
71 protected String ganador() { return jugadores[turno]; } // quien llega a 21 gana
72}
73
74public class Main {
75 public static void main(String[] args) {
76 Scanner sc = new Scanner(System.in);
77 if (!sc.hasNextLine()) return;
78 String cual = sc.nextLine().trim();
79 List<String> jugadas = new ArrayList<>();
80 while (sc.hasNextLine()) {
81 String l = sc.nextLine().trim();
82 if (!l.isEmpty()) jugadas.add(l);
83 }
84 Juego juego = switch (cual) {
85 case "juego nim" -> new Nim();
86 case "juego carrera" -> new Carrera();
87 default -> null;
88 };
89 if (juego == null) System.out.println("Juego desconocido: " + cual);
90 else juego.jugar(jugadas.iterator());
91 }
92}Los turnos, las jugadas no válidas y el final de partida están resueltos una vez para todos los juegos: cada uno solo pone sus reglas.
Así funcionan los motores de juegos y los frameworks: llaman a tus métodos cuando toca.
Test
Test: Template Method
0/5 respondidas · 0 aciertosElige una respuesta en cada pregunta: verás al momento si es correcta y por qué. Con un 80 % de aciertos se da por superada.
1.¿Qué hace el método plantilla?
2.¿Por qué el método plantilla suele ser final?
3.¿Qué es un «gancho» (hook)?
4.¿Cuál de estos usa el patrón Template Method?
5.¿En qué se diferencia de Strategy?