Patrón Estrategia – Videotutorial

El patrón estrategia es uno de los patrones de comportamiento más importantes y utilizados dentro de del grupo de patrones Gof. Tanto es así, que muchos lenguajes ofrecen características integradas en el propio lenguaje para hacer incluso más facil de utilizar este elemento de diseño cuando se implementa en uno de estos lenguajes. Uno de estos lenguajes es java.

Aqui os dejo un videotutorial que trata a nivel teórico el patrón de diseño Estrategia explicando sus beneficios y situaciones en la que deberia aplicarse.

Patrón Estrategia – Una clase para ordenar listas de elementos I

El patrón estrategia nos permite determinar el comportamiento en tiempo de ejecución de un objeto, seleccionando una estrategia diferente para el objeto antes de pedirle que realice de nuevo una operación.

Imaginemos que queremos definir un clase cuya finalidad sea ordenar una colección de objetos. Supongamos por ejemplo, que el tipo de los elementos de la colección sean Manzanas, y sus caracteristicas peso y color. El criterio para ordenar estos elementos de la colección en principio no es único. Podriamos ordenar por peso, por color, y a su vez de forma ascendente o descendente, etc. Si el tipo de elementos fuera otro distinto a Manzanas, por ejemplo Persona, se darían a su vez nuevos criterios.

Queda claro que existirán un sin fin de algoritmos o estrategias para indicar, dados dos elementos, cual debería preceder a cual en una hipotética ordenación.

La clase que ordena no tiene por qué conocer los detalles para discernir que elemento precede a otro, esta responsabilidad la puede delegar en otro objeto, su estrategia. Una vez pueda pedirle a su objeto estrategia que le indique, dados 2 elementos cualquiera, cual precede a cual, será capaz de ordenar una colección entera de elementos, a partir de los resultados que la estrategia le irá proporcionando cada vez que se le pida que compare 2 elementos de la colección para los cuales la clase que ordena necesite esta información durante el trascurso del algoritmo de ordenación que implementa internamente.

Comparer: La interfaz de la estrategia

Asi pues, la estrategia para comparar se define en los siguientes términos: una interfaz con un único método que recibe como argumentos dos referencias a los objetos a comparar y devuelve un valor menor que cero si el primero es menor, igual a cero sin son iguales y mayor que cero si el primero es mayor.

public interface Comparer<T> {

	int compare(T x, T y);

}

La interfaz es genérica en función del tipo T. Para comparar dos objetos tipo T se llama al método compare de la interfaz proporcionando como argumentos dos referencias a objetos tipo T y se obtiene un valor int. Cuando queramos comparar objetos Manzana, habrá que indicar como parámetro de tipo T el nombre de la clase: Apple.

La clase manzana: los items de la colección

Si utilizamos manzanas como elementos de la colección podemos definir la clase manzana mediante el siguiente código:

public class Apple {

	String color;
	double weight;

	public String getColor() {
		return color;
	}

	public void setColor(String color) {
		this.color = color;
	}

	public double getWeight() {
		return weight;
	}

	public void setWeight(double weight) {
		this.weight = weight;
	}

	public Apple(String color, double weight) {
		this.color = color;
		this.weight = weight;
	}

	@Override
	public String toString() {
		return "Apple [color=" + color + ", weight=" + weight + "]";
	}
}

Implementando la interfaz Comparer para definir una estrategia concreta

Podemos escribir una clase comparadora de manzanas, según su peso de menor a mayor, escribiendo una clase que implemente la interfaz IComparer y concretandola para T = Apple, es decir, implementando la inferfaz Comparer<Apple>.

public class ByWeightAppleComparer implements Comparer<Apple> {

	@Override
	public int compare(Apple x, Apple y) {		

if(x.getWeight() < y.getWeight()) return -1;
		if(x.getWeight() == y.getWeight()) return 0;
		if(x.getWeight() > y.getWeight()) return 1;

		return 0;
	}
}

La clase Sorter: delega en una estrategia de comparación

La clase que va a encargarse de realizar la ordenación de los elementos necesitará que se le proporcione una referencia a una intancia de un objeto de este tipo de estrategia (u otra cualquiera, son intercambiables objetos cuyo clase implemente la interfaz de la estrategia)
La clase Sorter utilizará como tipo del parámetro para recibir la referencia el tipo abstracto de la interfaz, no necesita conocer el tipo concreto para comunicarse con el objeto estrategia, utiliza la interfaz proporcionada por la interfaz abstracta común a todas las estrategias de comparación.

La clase Sorter será genérica para permitir ordenar colecciones de objetos de cualquier tipo especificado genericamente por T. Logicamente para ordenar colecciones de objetos de tipo T el sorter necesitará una estrategia comparadora de objetos de tipo T.

public class Sorter<T> {

	Comparer<T> comparer;

	// Interfaz para establecer la estrategia (un setter)
	public void setComparer(Comparer<T> comparer) {
		this.comparer = comparer;
	}

	public Sorter(Comparer<T> comparer) {
		this.comparer = comparer;
	}

	private int performComparison(T x, T y) {
		return comparer.compare(x, y);
	}

El método performComparison es el método en el cual la clase Sorter delega en la estrategia de comparación.
El campo comparer será de tipo Comparer de T, es decir, si se crea una instancia de Sorter concreta para elementos tipo manzana (T = Apple) Sorter, el comparador deberá ser a su vez un comparador de manzanas Comparer.

//Crear un sorter para ordenar manzanas por peso de menor a mayor
Sorter<Apple> appleSorter = new Sorter<Apple>(new ByWeightAppleComparer());

Y por último, el método de la clase Sorter que realiza la ordenación mediante el algoritmo de selección directa.

public void sort(T[] items) {

	for (int i = 0; i < items.length; i++) {
		// Asumir de partida que el valor menor de la parte aun no ordenada
		// del array esta en la primera posicion de la parte aun no ordenada
		int notOrderedLowerValueIndex = i;
		// Recorrer el resto del vector para encontrar otro menor
		for (int j = i + 1; j < items.length; j++)
			// Si se encuentra un valor menor en la posicion j
			if (performComparison(items[j],
					items[notOrderedLowerValueIndex]) < 0)
				notOrderedLowerValueIndex = j; // nueva posicion del menor j
			// Si el valor de la posicion i no estaba en su sitio
		// porque hemos encontrado en una posicion distinta otro valor aun
		// menor intercambiar las posiciones
		if (i != notOrderedLowerValueIndex) {
			T aux = items[i];
			items[i] = items[notOrderedLowerValueIndex];
			items[notOrderedLowerValueIndex] = aux;
		}
	}
}

En las lineas 20 y 30 se encuentra la llamada que necesita hacer el algoritmo de ordenación cuando necesita saber dados 2 elementos cual precede a cual. Para ello, llama al método performComparison de la clase, y este a su vez delega en el objeto estrategia elegido por el código cliente que instancia y utiliza un objeto de tipo Sorter.

El programa principal: hace de código cliente

El código cliente será, en el ejemplo, el método main de java.

public class Program {

	static Apple[] apples = new Apple[] { new Apple("verde", 125.0),
			new Apple("roja", 150.5), new Apple("amarilla", 200.0) };

	public static void main(String[] args) {

		//Crear un sorter para ordenar manzanas por peso de menor a mayor
		Sorter<Apple> appleSorter = new Sorter<Apple>(new ByWeightAppleComparer());
		appleSorter.sort(apples);
		printApples();

	private static void printApples() {
		for (Apple apple : apples) {
			System.out.println(apple.toString());
		}
	}

El resultado de ejecutar el código es el siguiente:

Apple [color=verde, weight=125.0]
Apple [color=roja, weight=150.5]
Apple [color=amarilla, weight=200.0]

El vector de manzanas ordenado por peso de menor a mayor.

La clase Sorter se puede reutilizar para ordenar manzanas por otros criterios (estrategias) o incluso otro tipo de objetos que no sean manzanas al ser génerica o parametrizada según el paramétro de clase T.