Ejemplos java y C/linux

Tutoriales

Enlaces

Licencia

Creative Commons License
Esta obra está bajo una licencia de Creative Commons.
Para reconocer la autoría debes poner el enlace http://www.chuidiang.org

Patrón estrategia

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

Vamos un poco más allá

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.

Comparación con la herencia.

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:

Estadísticas y comentarios

Numero de visitas desde el 4 Feb 2007: