Cuando tenemos un algoritmo que puede cambiar, por cualquier motivo, bien en tiempo de ejecución, bien cuando lo implementemos en otro sitio, y no queremos modificar el código que usa ese algoritmo, viene bien usar el patrón estrategia.
Básicamente, consiste en hacer una interface con los métodos del algoritmo. Luego implementamos las distintas versiones del algoritmo en clases que implementen esa interface. El código que utilice el algoritmo debe admitir que se le pase desde fuera una interface de ese algoritmo y usa la interface. De esta forma, nunca ve la implementación concreta del algoritmo y puede cambiarse incluso en tiempo de ejecución.
Por ejemplo, si queremos poder cambiar el algoritmo que ordena una array, podemos hacer las siguientes clases (java)
public inteface InterfaceOrdena
{
public Object[] ordena (Object [] unArray);
}
public class OrdenaQuickSort implements InterfaceOrdena
{
public Object [] ordena (Object [] unArray)
{
/* Aquí
el código de ordenar por QuickSort */
}
}
public class OrdenaBurbuja implements InterfaceOrdena
{
public Object [] ordena (Object [] unArray)
{
/* Aquí
el código de ordenar por Burbuja */
}
}
El código que utiliza esto puede ser algo como esto
public class UnoQueNecesitaOrdenar
{
/** Se le pasa el algoritmo
para ordenar */
public void setAlgoritmoOrdenar (InterfaceOrdena algoritmoOrdenar)
{
this.algortimoOrdenar = algoritmoOrdenar;
}
/** Algortimo para ordenar
*/
private InterfaceOrdena algoritmoOrdenar;
/** Método que utiliza
el algoritmo de ordenar */
public void hazCosas ()
{
Object [] array;
// Aquí
código que crea un array
Object [] arrayOrdenado = algoritmoOrdenar.ordena
(array);
// Aquí
sigue el código
}
}
Todo claro hasta aquí. El algoritmo se puede cambiar incluso en tiempo de ejecución. Si en algún momento implementamos algún algoritmo nuevo, no necesitamos ni siquiera recompilar la clase UnoQueNecesitaOrdenar
Lo primero que podemos plantearnos es ¿qué es un algoritmo?. Podemos considerar como algoritmo algo más general que lo que habitualmente entendemos por algoritmo. Cualquier trozo de código que haga algo puede considerarse un algoritmo.
De esta forma, en una clase, cualquier trozo de código que preveamos que puede cambiar, bien cuando vayamos a utilizar esta clase en otro sitio, bien porque tengamos que hacer otras clases muy similares en las que ese trozo de código sea distinto, podemos utilizar el patrón estrategia.
Un ejemplo concreto. Supongamos que vamos a hacer un JTextField que recoja números entre 0 y 10. Lo habitual sería hacer algo como esto:
Todo correcto hasta aquí. Sin embargo, podemos prever que otro día querremos algo que nos de un double, un Date o una IP en vez de un int. También es posible, por ejemplo, que necesitemos que nos de un int, pero que el operador lo escriba en hexadecimal o en números romanos. También quizás necesitemos, por ejemplo, que el Date que queremos pedir al operador debe ser obligatoriamente un Domingo, que el int que pedimos debe ser un número primo, etc, etc.
Si pensamos un poco, hay dos "algoritmos" claros:
Podemos implementar para ambos algoritmos el patrón estrategia. Puede ser algo como esto:
public interface StringAObject
{
public Object parse (String cadena) throws
ParseException;
}
public class StringADouble implements StringAObject
{
public Object parse (String cadena) throws
ParseException
{
/* Para implementar
esto se puede usar Double.parseDouble (String) */
}
}
public class StringADate implements StringAObject
{
public Object parse (String cadena) throws
ParseException
{
/* Para implementar
esto se puede usar Date.valueOf (String) */
}
}
Y para comprobar si cumple las restricciones
public interface Restriccion
{
public boolean pasaRestriccion (Object
valor);
}
public class Rango implements Restriccion
{
public boolean pasaRestriccion (Object
valor)
{
/*
valor debe ser un Double. Si está entre minimo y maximo, devolvemos
true. */
}
}
public class Festivo implements Restriccion
{
public boolean pasaRestriccion (Object
valor)
{
/* valor debe ser un Date. Si es festivo devolvemos true. */
}
}
En cuanto al editor, sería algo como esto
public class Editor extends JTextField
{
/** Se le pasa el conversor
de String a Object que queremos que use */
public
void setConversor (StringAObject conversor)
{
this.conversor
= conversor;
}
/** Se le pasa la restricción
que debe cumplir el Object */
public void setRestriccion (Restriccion restriccion)
{
this.restriccion = restriccion;
}
/** Conversor de String
a Object que va a usar el editor. */
private StringAObject conversor;
/** Restricción
que debe cumplir el Object */
private Restriccion restriccion;
/** Recoge el String
del JTextField, lo convierte a Object, comprueba que cumple las restricciones
y lo devuelve */
public Object dameValor()
{
String cadena = this.getText();
Object valor;
try
{
valor = conversor.parse (cadena);
}
catch (ParseException
e)
{
//
Mostrar aviso
return null;
}
if (!restriccion.pasaRestriccion(valor))
{
//
Mostrar aviso
return null;
}
return valor;
}
}
Bien, ya tenemos nuestro Editor genérico. Hay que inicializarlo pasándole un StringAObjeto y un Restriccion. Para facilitar el asunto, se puede ahora heredar de este editor para hacer editores específicos. En el constructor de cada clase hija se pasaría un StringAObjeto y una Restriccion, ahorrando trabajo al que los quiera usar. Estos editores hijos, pueden además incluir métodos específicos de configuración o que permitan obtener el valor de forma más cómoda.
Por ejemplo, si queremos hacer nuestro editor de doubles, heredamos y podemos ponerle métodos setMaximo(double maximo), setMinimo (double minimo) para fijar los límites (por supuesto, StringADouble debería tener también estos métodos) y se podría añadir también el método double dameDouble () que devuelva el tipo específico que necesitamos.
Otra posible solución para este problema sería hacer el Editor con dos métodos abstractos, el de Object parse(String cadena) y el de boolean pasaRestriccion(Object valor). Luego se heredan editores concretos que implementen estos dos métodos.
Esta solución tiene varias pegas respecto al patrón estrategia: