El patrón de diseño Template Method.
0. Índice de contenidos.
- 1. Introducción.
- 2. Entorno.
- 3. Descripción del patrón.
- 4. Aplicando el patrón.
- 5. El «método plantilla».
- 6. Las subclases.
- 7. Probando el «método plantilla».
- 8. Referencias.
- 9. Conclusiones.
1. Introducción.
Como bien sabemos, el código duplicado es el gran enemigo del «código limpio». Existe un amplio abanico de recursos para intentar eliminarlo. Algunos patrones de diseño tienen como objetivo eliminar código duplicado.
En este tutorial intentaremos explicar cómo eliminar código duplicado con ayuda del patrón de diseño Template Method y los casos en los que puede ser interesante usarlo.
2. Entorno.
El tutorial está escrito usando el siguiente entorno:
- Hardware: Portátil MacBook Pro 15′ (2.2 Ghz Intel Core I7, 4GB DDR3).
- Sistema Operativo: Mac OS Snow Leopard 10.6.7
- Entorno de desarrollo: Eclipse 3.7 Indigo.
3. Descripción del patrón.
El patrón de diseño Template Method forma parte de la familia de patrones denominados de comportamiento. Este tipo patrones ayudan a resolver problemas de interacción entre clases y objetos.
Este patrón nace de la necesidad de extender determinados comportamientos dentro de un mismo algoritmo por parte de diferentes entidades. Es decir, diferentes entidades tienen un comportamiento similar pero que difiere en determinados aspectos puntuales en función de la entidad concreta.
Una posible solución podría ser copiar el algoritmo en cada de las diferentes entidades cambiando la parte concreta en la que difieren. Esta solución tiene una consecuencia negativa ya que se genera código duplicado.
La solución que propone el patrón Template Method es abstraer todo el comportamiento que comparten las entidades en una clase (abstracta) de la que, posteriormente, extenderán dichas entidades. Esta superclase definirá un método que contendrá el esqueleto de ese algoritmo común (método plantilla o template method) y delegará determinada responsabilidad en las clases hijas, mediante uno o varios métodos abstractos que deberán implementar.
Como puede verse en el anterior diagrama, la superclase contiene el método plantilla, ese método con el algoritmo que comparten las entidades concretas (subclases). Como se puede apreciar, define una o varias operaciones concretas en forma de métodos abstractos que son usados por el método plantilla y que deben ser implementadas por las clases hijas. Dichos métodos abstractos representan los comportamientos concretos de las entidades.
4. Aplicando el patrón.
Como suele pasar con la mayoría de patrones de diseño, la mejor forma de entenderlos es con un ejemplo…
Imaginemos que tenemos que desarrollar un pequeño programa que nos de una valoración de los jugadores de un equipo de fútbol en función de ciertos parámetros. Supongamos que el equipo está compuesto por porteros y delanteros (los defensas y centrocampistas nos los saltaremos…). El programa deberá comportarse de la siguiente manera:
- Para cada jugador devolverá una valoración: malísimo, malo, normal, bueno o galáctico.
- Todo jugador que haya disputado menos del 20% de los minutos que disputó su equipo será considerado malísimo.
- Los jugadores suman puntos positivos si alcanzan unos objetivos.
- El objetivo de los delanteros es marcar goles, sumarán 30 puntos por cada gol por partido que promedien. Ejemplo: si un jugador marca de media 0,8 goles por partido sumará 24 puntos.
- El objetivo de los porteros es no recibir goles, perderán 30 puntos por cada gol por partido que promedien, pero partirán con 50 puntos. Ejemplo si un portero encaja 1,1 gol por partido sumará 17 puntos (50 – 30*1,1), si recibiese 0,5 goles por partido sumaría 35 puntos (50 – 30*0,5).
- A los jugadores que más cobren se les exigirá más, por lo que los jugadores perderán puntos en función de su salario. Imaginemos que el salario de los jugadores oscila del 1 al 15.
- Los delanteros perderán el 10% de su salario en puntos. Ejemplo: si un delantero cobra 12 perderá 1,2 puntos.
- Los porteros perderán el 8% de su salario en puntos. Ejemplo: si un portero cobra 12 perderá 0,96 puntos.
- Los jugadores que obtengan menos de 0 puntos serán considerados malísimos.
- Los jugadores que obtengan entre 0 y 10 puntos serán considerados malos.
- Los jugadores que obtengan entre 10 y 20 puntos serán considerados normales.
- Los jugadores que obtengan entre 20 y 40 puntos serán considerados buenos
- Los jugadores que obtengan más de 40 puntos serán considerados galácticos.
5. El «método plantilla».
Antes de nada definiremos nuestra superclase Jugador, que contendrá todo el comportamiento y elementos comunes de cualquier tipo de jugador (ya sean delanteros o porteros).
Observemos las principales características de la clase:
- La clase es abstracta, por lo que un jugador por sí solo no es nada. Habrá que precisar si es delantero o portero.
- Contiene las propiedades comunes de cualquier jugador: nombre, minutos jugados y salario.
- Contiene un «enum» denominado ValoracionJugador que representará la valoración que se da a un jugador.
- Contiene el método plantilla calculaValoracion que define el esqueleto de la lógica de cálculo de valoración de un jugador, implementando las partes comunes y delegando las clases concretas en las subclases.
- Contiene los métodos abstractos: calculaPuntosPorObjetivos y getPuntosPenalizacionPorSalarioAlto que deben ser implementados por las subclases (en función de si es delantero o portero). Obsérvese que el método plantilla (método calculaValoracion) hace uso de ellos.
public abstract class Jugador { public enum ValoracionJugador { MALISIMO(-9999, 0), MALO(0, 10), NORMAL(10, 20), BUENO(20, 40), GALACTICO( 40, 9999); private int valorMinimo; private int valorMaximo; private ValoracionJugador(int valorMinimo, int valorMaximo) { this.valorMinimo = valorMinimo; this.valorMaximo = valorMaximo; } boolean isInRango(float puntos) { return puntos >= valorMinimo && puntos
6. Las subclases.
LLegó el momento de definir los comportamientos concretos o, lo que es lo mismo, de extender de la clase Jugador. En este punto implementaremos la forma de calcular los puntos por objetivos y los puntos de penalización por salario alto, tanto para delanteros como para porteros.
La clase Delantero quedaría de la siguiente forma:
public class Delantero extends Jugador { private final int golesMarcados; public Delantero(String nombre, int minutosJugados, int salario, int golesMarcados) { super(nombre, minutosJugados, salario); this.golesMarcados = golesMarcados; } @Override public float calculaPuntosPorObjetivos() { return 30 * (golesMarcados / super.getPartidosJugados()); } @Override public float getPuntosPenalizacionPorSalarioAlto() { return (float) (salario * 0.1); } }
La clase Portero quedaría así:
public class Portero extends Jugador { private final int golesEncajados; public Portero(String nombre, int minutosJugados, int salario, int golesEncajados) { super(nombre, minutosJugados, salario); this.golesEncajados = golesEncajados; } @Override public float calculaPuntosPorObjetivos() { return 50 - (30 * golesEncajadosPorPartido()); } private float golesEncajadosPorPartido() { return golesEncajados / super.getPartidosJugados(); } @Override public float getPuntosPenalizacionPorSalarioAlto() { return (float) (salario * 0.08); } }
7. Probando el «método plantilla».
LLegados a este punto ya estamos en disposición de probar nuestro método plantilla. Creamos una clase main e introducimos los parámetros de cuatro jugadores: dos porteros y dos delanteros.
public class Prueba { public static void main(String args[]) { final int TOTAL_MINUTOS_JUGADOS_EQUIPO = 360; final Jugador jugador1 = new Delantero("Cristiano Ronaldo", 235, 14, 4); escribeValoracionJugador(jugador1, TOTAL_MINUTOS_JUGADOS_EQUIPO); final Jugador jugador2 = new Portero("Iker Casillas", 360, 10, 2); escribeValoracionJugador(jugador2, TOTAL_MINUTOS_JUGADOS_EQUIPO); final Jugador jugador3 = new Delantero("Messi", 280, 13, 0); escribeValoracionJugador(jugador3, TOTAL_MINUTOS_JUGADOS_EQUIPO); final Jugador jugador4 = new Portero("Victor Valdés", 360, 11, 6); escribeValoracionJugador(jugador4, TOTAL_MINUTOS_JUGADOS_EQUIPO); } private static void escribeValoracionJugador(Jugador jugador, int totalMinutosJugadosEquipo) { System.out.println("El jugador " + jugador.getNombre() + " obtuvo una valoración de " + jugador.calculaValoracion(totalMinutosJugadosEquipo)); } }
Y el resultado de ejecutar este método main sería el siguiente:
El jugador Cristiano Ronaldo obtuvo una valoración de GALACTICO El jugador Iker Casillas obtuvo una valoración de BUENO El jugador Messi obtuvo una valoración de MALISIMO El jugador Victor Valdés obtuvo una valoración de MALO
Espero que no se hayan notado mucho mis preferencias futbolísticas :-).
8. Referencias.
9. Conclusiones.
En este tutorial hemos visto cómo combatir el problema del código duplicado entre diferentes entidades gracias al patrón de diseño Template Method.
Me gustaría recordar que los patrones de diseño aportan soluciones a muchos otros problemas, no solo a la eliminación del código duplicado. Intentaremos ir explicándolos poco a poco.
Espero que os haya sido de ayuda. Un saludo.
Miguel Arlandy
Te ha faltado poner que si un jugador ficha por el Atleti, su rendimiento baja un 300% con respecto al que tenía en su anterior equipo. Por lo demas, veo que eres muy poco parcial y tratas igual al Madrid que al Barça, jeje
Venga pero que bien explicado, ademas sabes mucho de fútbol jaja ¡HALA MADRID!
No compila. Dice que el método «calculaValoracion» no está definido.
Felicitaciones, tu escrito es claro para mi que no tengo formación en ingeniería de sistemas, pero debo esquematizar un problema con éste tipo de patron de arquitectura sw.
Me sale un error de valoracion: en mi Mac, dice: «¿Quien es Cristiano Ronaldo?», ademas de escribir: «Messi mas que un galcatico»… 😛 :-))
Falta parte del código en la primera imagen
Falta parte de códido en la clase abstracta
La clase Jugador está incompleta. Porfavor pon el codigo completo.