icono_twiter icono LinkedIn
Miguel Arlandy Rodríguez

Consultor tecnológico de desarrollo de proyectos informáticos.

Puedes encontrarme en Autentia: Ofrecemos servicios de soporte a desarrollo, factoría y formación

Somos expertos en Java/JEE

Ver todos los tutoriales del autor

Fecha de publicación del tutorial: 2011-10-04

Tutorial visitado 7.519 veces Descargar en PDF
El patrón de diseño Template Method.

El patrón de diseño Template Method.


0. Índice de contenidos.


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 < valorMaximo;
		}
	}

	protected final String nombre;

	protected final int minutosJugados;

	protected final int salario;

	private static final int MINUTOS_PARTIDO = 90;

	public Jugador(String nombre, int minutosJugados, int salario) {
		this.nombre = nombre;
		this.minutosJugados = minutosJugados;
		this.salario = salario;
	}

	// el "método plantilla"
	public ValoracionJugador calculaValoracion(int totalMinutosEquipoEnTemporada) {
		if (juegaMenosDel20PorCientoDeLosMinutos(totalMinutosEquipoEnTemporada)) {
			return ValoracionJugador.MALISIMO;
		}
		final float puntos = calculaPuntosPorObjetivos()
				- getPuntosPenalizacionPorSalarioAlto();
		return getValoracionEnFuncionDePuntos(puntos);
	}

	private boolean juegaMenosDel20PorCientoDeLosMinutos(
			int totalMinutosEquipoEnTemporada) {
		return minutosJugados < totalMinutosEquipoEnTemporada * 0.2;
	}

	protected abstract float calculaPuntosPorObjetivos();

	protected abstract float getPuntosPenalizacionPorSalarioAlto();

	private ValoracionJugador getValoracionEnFuncionDePuntos(float puntos) {
		for (ValoracionJugador valoracion : ValoracionJugador.values()) {
			if (valoracion.isInRango(puntos)) {
				return valoracion;
			}
		}
		throw new IllegalArgumentException(
				"Cantidad de puntos no cuantificable " + puntos);
	}

	protected float getPartidosJugados() {
		return (float) this.minutosJugados / MINUTOS_PARTIDO;
	}
	
	public String getNombre() {
		return this.nombre;
	}
	
}



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

marlandy@autentia.com

A continuación puedes evaluarlo:

Regístrate para evaluarlo

Por favor, vota +1 o compártelo si te pareció interesante

Share |
Anímate y coméntanos lo que pienses sobre este TUTORIAL:

Fecha publicación: 2011-10-16-17:26:29

Autor: andrs

Hola Miguel:

Algo me dice que el método aplicado es muy brillante, y el algoritmo clasificador muy deficiente a la vista de los resultados (Mesi <<< CR7).

Buen artículo.

Fecha publicación: 2011-10-04-09:55:48

Autor: drarenas

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