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

La última mejora que podemos llevar a cabo va a permitir facilitar el uso del sorter desde el código cliente. Vamos a ver como. Si nuestro sorter ordena listas de objetos, como por ejemplo manzanas, y estos objetos se distinguen por poseer una serie de caracteristicas o propiedades, en el caso de las manzanas color y peso; podemos hacer que el codigo cliente solo tenga que elegir cual es la caracteristica de entre todas las que tiene una clase de ojetos para ordenarlos en base a esta propiedad elegida.

Qué caracteristica vamos a escoger para ordenar sera la estrategia, que llamaremos selector

public interface Selector<TSource, TResult> {
	TResult select(TSource item);
}

Como vemos está parametrizado para dos tipos, el tipo de entrada TSource y el tipo de resultado. En nuestro caso TSource será el tipo del elemento, Manzana y TResult será el tipo de la caracteristica elegida, String si es el color o double si se trata de el peso.

La interfaz define un único método select que recibe un TSource y devuelve un TResult. En el caso de seleccionar el color de las manzanas Apple y String respectivamente.

Veamos 2 declaraciones de estrategias en el método main de la clase principal del programa.

//Declara las dos estrategias de seleccion
Selector<Apple, String> colorSelector = (Apple a) -> a.getColor();
Selector<Apple, Double> weightSelector = a -> a.getWeight();

En el segundo selector, en la expresión lambda no hemos especificado el tipo de la entrada a a la expresión, el compilador la infiere observando que la expresión lambda se asigna a la variable weightSelector, que es de tipo Selector, y como el método select de la interfaz es TResult select(TSource item) y dado que el selector ha sido definido de forma geerica Selector queda claro para el compilador que TSource es Apple.

Veamos ahora como queda la nueva sobrecarga del método sort que acepta un selector.

	public static <TSource, TResult extends Comparable<TResult>> void sort(
			TSource[] items, Selector<TSource, TResult> selector) {

		Comparer<TSource> comparer = 
				(x, y) -> selector.select(x).compareTo(selector.select(y));

		sort(items, comparer);	
	}

Lo primero que observamos, es que usa el selector para definir un comparador mediante una expresión lambda formada a partir de llamadas al selector y luego delega en el método sort anterior que utilizaba estrategias de comparación para su funcionamiento parsandole como argumento ese comparador construido internamente.

La firma del método es un poco complicada por el hecho de que se está aplicando una restricción al parámetro de clase TResult: TResult extends Comparable. Como TResult no sirve cualquier tipo de clase, si no solamente aquellas que sean de tipo Comparable o sus subclases. Esto garantiza que dado que TResult implementa Comparable entonces se podrá llamar al método compareTo, que es justo lo que hace la expresión lambda con el valor devuelto por el selector.

La expresion lambda selecciona mediante el selector la misma característica en ambos objetos x e y, y puesto que queda garantizado que la característica es de tipo Comparable o una subclase se puede usar el método compareTo con ellas.

Entonces, ¿óomo será el código para ordenar por color o por peso?

Sorter.sort(apples, weightSelector); 
printApples();
	
Sorter.sort(apples, colorSelector); 
printApples();

Incluso podemos ser más directos y no crear variables intermedias escribiendo la expresión lambda directamente en la llamada. Es como si un comportamiento también fuese considerado un dato de entrada en la llamada.

Sorter.sort(apples, a -> a.getColor());
printApples();

En este caso, como la caracteristica la devuelve un método, el getter, podemos utilizar la nueva característica de java consistente la capacidad para asignar a variables de interfaz referencias directas a nombres de métodos.

Sorter.sort(apples, Apple::getWeight);
printApples();

El método getWeight() es un método de instancia, lo que quiere decir que en realidad recibe un argumento más en su llamada además de los que explicitamente se definen en su firma. Ese argumento adicional es la instancia que lo invoca, es decir, la llamada será x.getWeight() o y.getWeight(), etc y esa x o esa y que son referencias a objetos Apple son «argumentos» de entrada en la llamada. Esto es lo que hace que x.getWeight() devuelva el peso de la manzana x y y.getWeight() el peso de la manzana y, x e y «parametrizan» el resultado de la llamada.

Es por eso que la expresión Apple::getWeight es válida para asignar a una interfaz Selector cuyo método es TResult select(TSource source) El método select necesita un argumento, aunque getWeight() no recibe ninguno, pero eso es de forma explicita porque al ser un método de instancia si que recibe la refencia al objeto que lo invoca x si hicieramos x.getWeight(). Por tanto, a efectos prácticos es como si se sustituyera por una versión estática getWeight(Apple a) que ya es compatible con el método select de la interfaz.

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

Seguimos con el ejemplo del sorter de posts anteriores. Vamos a ver algunas mejoras en la usabilidad. En primer lugar la forma ordenar mediante una nueva estrategia consiste en cambiar de estrategia llamaando al método setComparer() proporcionando la referencia al objeto comparador y despues llamar al metodo sort una vez que el sorter cuenta con su nuevo comparador.

Podemos hacer que esto esto suceda en un solo paso si sobrecargamos el método sort para reciba ademas de la lista de elementos a ordenar, un segundo argumento que será directamente la estrategia a aplicar. En este caso, el método puede ser estatico porque no necesita acceder a ningún campo de instancia de la clase. ¡No es necesario instanciar un Sorter para usar la ordenación!

public static <T> void sort(T[] items, Comparer<T> comparer) {

		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 (comparer
						.compare(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;
			}
		}
	}

Ver como se realizar la llamada, se hace uso directamente de la clase Sorter, no es necesario instanciar un objeto Sorter.

//Usa la version estática del método sort con la estrategia como argumento de entrada
Sorter.sort(apples, byWeight);
printApples();

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

En el post anterior hemos visto como definir una clase para ordenar colecciones de elementos de cualquier tipo y criterio de ordenación, elegido desde fuera de la clase Sorter por el codigo cliente, gracias al patrón estrategia.

Hemos visto que para definir una nueva estrategia es suficiente con crear una clase que implemente la interfaz de la estrategia, como en el caso de la clase ByWeightAppleComparer.

Ahora vamos a ver otras formas para proporcionar la estrategia con la que una instancia Sorter va a comparar los elementos de la colección sin que sea necesario crear una nueva clase.

Forma 1: Mediante clases anonimas

Creamos un nuevo objeto uilizando directamente el tipo de la interfaz. Lo podemos guardar en una variable del tipo de la interfaz y luego pasarselo como argumento al constructor de la clase Sorter cuando construimos un objeto Sorter.

Comparer<Apple> comparer = new Comparer<Apple>() {

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

		return Double.compare(x.getWeight(), x.getWeight());
	}
};		

Sorter<Apple> appleSorter = new Sorter<Apple>(comparer);
appleSorter.sort(apples);
printApples();

Tambien podiamos haber cambiado la estrategia de un objeto Sorter existente. Recordemos que gracias, al patrón estrategia, el comportamiento del objeto sorter es dinámico, puede cambiarse en tiempo de ejecución. Veamos, ahora como hacer que ordene por color.

appleSorter.setComparer(new Comparer<Apple>(){

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

				return x.getColor().compareToIgnoreCase(y.getColor());
			}});

appleSorter.sort(apples);
printApples();

Para cambiar la estrategia de un objeto Sorter simplemente se llama al método setComparer proporcionando la referencia a la nueva estrategia. En este caso la proporcionamos directamente conforme se crea, sin almacenarla en una variable puesto que no vamos a necesitar la referencia nuevamente en el código cliente.

Forma 2: Mediante expresiones lambda

El segundo caso, es un caso típico, pero a la vista del código podemos apreciar que sólo la linea 6 aporta algo de información util, esto es, la forma de comparar por color, todo lo de alrededor es puro envoltorio, lo cual complica ver la parte esencial, el código de comparación de la linea 6 que queda enterrado entre tanta paja sintáctica. Las clases anonimas tienen este problema, que se soluciona con una característica nueva que incorpora Java 8: las expresiones lambda. Para más detalle sobre las expresiones lambda podeis mirar mi otro post Expresiones lambda.

La interfaz de la estretagia Comparer define único método, lo que la convierte en candidata a ser usada junto con expresiones lambda. Podemos asignar una expresión lambda a una variable del tipo de la interfaz.

public interface Comparer<T> {

	int compare(T x, T y);
}

La expresión lambda, para ser compatible, debe contar en su izquierda con dos identificadores y en la parte derecha debe haber una expresión que de como resultado un int.

//Un comparador por peso mediante expresion lambda
Comparer<Apple> byWeight = (x, y) -> Double.compare(x.getWeight(), y.getWeight());

Podemos ver que la sintaxis es ahora mucho más concisa.

sorter.setComparer(byWeight);
sorter.sort(apples);
printApples();

Incluso podemos escribir directamente la expresión lambda como argumento de entrada en la llamada al constructor de un Sorter.

//Crear un sorter proporcionando su estrategia de comparación
Sorter<Apple> sorter = new Sorter<Apple>(
				(x, y) -> x.getColor().compareToIgnoreCase(y.getColor())
				);

sorter.sort(apples);
printApples();

Por tanto, no es necesario crear ninguna clase ni objeto anónimo, directamente se proporciona al objeto sorter la expresión con la estrategia para comparar. Siendo el código mucho más claro, limpio y directo.

Expresiones lambda en java 1.8

Una expresión lambda equivale a escribir una especie de función matemática del tipo

f(x) = x * 2 + 3

En este caso leemos que dado un x, el resultado será el de multiplicar por 2 el valor de x y sumarle 3.

Para expresar esto mismo con una expresión lambda hariamos:
(x) -> x * 2 + 3

Expresar esto en programación requiría escribir un método o función, que a su vez debe encapsulsarse en una clase.

public class Contenedora implements Funcion {

	int funcion(int x) {
		return 2 * x + 3;
	}
}

Si la clase contenedora implementa una interfaz Funcion definida de esta manera

public interface Funcion {

	int funcion(int x); 
}

Entonces podemos asignar una referencia a un objeto de tipo Contenedora a una variable de tipo Funcion
y utilizar la variable para invocar al objeto que realiza la operación.

Funcion f1 = new Contenedora();
f1.funcion(25);     // 25 * 2 + 3

Todo este envoltorio en clases es necesario debido a que anteriormente java no permitia asignar funciones a variables, solamente se podían asignar valores de tipos de datos primitivos y referencias a objetos. Las funciones eran elementos de segunda categoría y se tenían que encapsular en clases contenedoras.

A partir de Java 8 si una interfaz como la que hemos definido, Funcion, contiene un único método, se podrá asignar directamente a una variable del tipo de la interfaz una función cuya firma sea compatible con la firma de este método.

class Funciones {
static int dobleMas3(int x) {
		return 2 * x + 3;
	}

//mas funciones dentro de la clase
}

Podemos usar la funciín de arriba dobleMas3 de este modo:

Funcion f1 = Funciones::dobleMas3;
f1.funcion(25);     // 25 * 2 + 3

A la variable f1, de tipo Funcion (una interfaz de un único método) le asignamos directamente el nombre de una función.

Pero existe un detalle que todavía se puede mejorar: las clases anónimas permiten asignar a una variable del tipo de la interfaz una referencia a un objeto nuevo anónimo creado así porque no existe una clase entre las que implementan la interfaz que reuna las características que buscamos y solamente vamos a crear una instancia objeto sola esa vez. ¿Para qué crear una clase implementadora de la interfaz a propósito para luego sólamente utilizar una instancia de esa clase? Es el caso de los «listeners» de Swing

Bien, pues esta misma situación también se puede presentar cuando estamos considerando funciones. ¿Y si no existe en el código una función que realice las instrucciones que yo quiero llevar a cabo?¿La defino en alguna parte, le doy un nombre y luego la asigno como hemos visto en el ejemplo de la función dobleMas3?

La respuesta es que no es necesario, igual que existen los objetos anónimos existen las funciones anónimas, y estas son las denominadas expresiones lambda.

Por tanto, supongamos que queremos asignar a esa variable f1 una función que no existe, en este caso haríamos algo así:

Funcion f1 = (x) -> x * 2 + 3; //expresion lambda
f1.funcion(25);     // 25 * 2 + 3

Una vez visto el ejemplo, vayamos a la sintaxis. Una expresión lambda se compone de 2 partes: parte izquierda y parte derecha, separadas por los caracteres ->. En la izquierda es donde se indican las variables de la expresión y en la derecha está el calculo que se realiza con esos valores de entrada proporcionados.

Se han de cumplir una serie de requisitos para que una expresión lambda se puede asignar a una variable (o pasarse como argumento en una llamada)

  1. La expresión lambda debe definir en su parte izquierda tantos identificadores como el número de parámetros que tiene la función única definida en la interfaz de cuyo tipo es la variable o parámetro de la llamada al que queremos asignarle la expresión.
  2. La expresión que realiza el cálculo en la parte derecha, cuando se evalue, debe proporcionar un resultado cuyo tipo debe coincidir con el tipo de valor de retorno que define la función en la interfaz

Como consecuencia de todo esto, podemos concluir que a partir de este momento, comportamiento o funcionalidad acaban convirtiéndose en información almacenable en variables y es posible pasarlo a otras funciones que la esperan como argumento de llamada. De modo que, igual que el resultado de una función depende de los valores con los que es invocada, el comportamiento de la función puede cambiar si al llamar a la función pasamos como argumento distintos comportamientos (funcionalidad o código). La implementación de un método de una clase puede parametrizar no sólo información para llevar a cabo su tarea sino que además puede parametrizar comportamientos (fragmentos de código) en los cuales delegar. En otras palabras, una parte del código pueda porporcionar no solo datos sino también instrucciones o código a otra parte cuando esta última es invocada.

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.