// // // // // // //

jueves, 5 de febrero de 2015

Enums y el Patrón Estrategia

    Hoy toca hablar de los enums en Java y de cómo usarlos junto con el Patrón Estrategia. En Java, los enums son mucho más potentes que en la mayoría de los lenguajes, en los que solo se guarda una palabra con un número.

    Una de las razones de su potencia es que almacenan objetos y, que además, poseen métodos bastante útiles como toString(), que lo sobrescribe de la clase Object, que devuelve el nombre del enumerado que lo ejecuta. Por ejemplo, EnumPrueba.PRIMERO.toString() devolvería “PRIMERO”. Otro método bastante potente es el método valueOf(String nombre), que devuelve un objeto Enum a partir de un String con su nombre. Por ejemplo, EnumPrueba.valueOf(“PRIMERO”) devolvería el objeto EnumPrueba.PRIMERO.

    Que enumeren objetos tiene una serie bastante grande de ventajas. Una de ellas, bastante inmediata, es que puedes usarlos para almacenar más de una cosa. Por ejemplo, descuentos. ¿Qué se puede guardar de ellos? Pues su Descripción, el valor del Descuento e, incluso, ¡El algoritmo para saber si un precio u objeto es susceptible de usar ese descuento! Pero veamos el código: Una clase producto, con varios campos, entre ellos, un objeto del Enum Tipo:
public class Producto {

    private String nombre;
    /* No debería usarse para los precios, pero para simplificar el ejemplo lo haré */
    private float precio;    
    private Tipo tipo;       // También podemos usar enums para esto!!!

    public Producto(String nombre, float precio, Tipo tipo) {
        this.nombre = nombre;
        this.precio = precio;
        this.tipo = tipo;
    }

    // Getters y Setters...
}
El Enum Tipo:
public enum Tipo {
    
    // Aquí se definen las constantes, como son objetos, pueden tener diferentes campos.
    JUGUETES("Juguetes para todos los públicos"),HOGAR("Útiles para el hogar");    
    
    // A partir de aquí se define la clase, 
    //que define el funcionamiento de los objetos del enum
    // En este caso tienen un campo descripción
    String  descripcion;

    // Es el constructor, cuando se definen arriba las constantes, 
    //estás deben tener los mismos argumentos
    // que uno de sus constructores, como un objeto cualquiera.
    private Tipo(String descripcion) {
        this.descripcion = descripcion;
    }

    public String getDescripcion() {
        return descripcion;
    }     
}
Aquí está el Enum de Descuentos y la Interface del Algoritmo.
public enum Descuentos {

    // Como veis, se pueden usar lambdas para hacer las clases anónimas,
    // o clases anónimas directamente
    // Esto es porque AlgoritmoDescuento es una interfaz funcional, o sea,
    // que solo tiene un método.
    MAYOR200("Descuento para mayores de 200", 30,
            (Producto p) -> p.getPrecio() > 200),
    ENTRE100Y200("Descuento para precios entre 100 y 200", 20,
            (Producto p) -> p.getPrecio() >= 100 || p.getPrecio() <= 200),
    DESJUGUETES("Descuento para juguetes", 10, new AlgoritmoDescuento() {

        @Override
        public boolean comprobarProductoValidoDescuento(Producto p) {

            return p.getTipo().equals(Tipo.JUGUETES);
        }
    }),
    NODESCUENTO("No hay descuento!! Te jodes!", 0, (Producto p) -> false);

    private final String descripcion;
    private final int descuento;
    private final AlgoritmoDescuento algoritmo;

    private Descuentos(String descripcion, int descuento, AlgoritmoDescuento algoritmo){
        this.descripcion = descripcion;
        this.descuento = descuento;
        this.algoritmo = algoritmo;
    }

    public String getDescripcion() {
        return descripcion;
    }

    public int getDescuento() {
        return descuento;
    }

    public boolean compruebaProductoValido(Producto p) {

        return algoritmo.comprobarProductoValidoDescuento(p);
    }

    // Este es un buen caso de método static, pues no usa campos del objeto,
    // si no que trabajo con
    // objetos estáticos y argumentos. Este método sirve para calcular 
    // cual de todos los descuentos se deberá usar.
    public static Descuentos comprobarTotalDescuentosProducto(Producto p) {

        // Aunque no haría falta crear este array por que el método 
        // te va devolver la misma referencia todo el rato, es unas 4-5 veces 
        // más rápido acceder a un campo local que ejecutar un método
        Descuentos[] descuentos = Descuentos.values();
        
        // Creo una referencia donde se guardará el decuento que se aplicará
        // y la inicializo con el NODESCUENTO, para ahorrar un 
        // if(descuento... == null) ya que este es el minimo
        // descuento que se aplica, y se aplicará si o si.
        Descuentos descDefinitivo = Descuentos.NODESCUENTO; 
        for (Descuentos d : descuentos) {            
            
            // El operador &amp;&amp; es como efectuar un if y un else if, así,
            // si el descuento es menor que el actual, nos ahorramos procesar 
            // si el producto es válido, que, en ocasiones, tardará más 
            // en comprobarse que una simple comparación entre ints
            if(descDefinitivo.getDescuento() > d.getDescuento()
                                             && d.compruebaProductoValido(p))
                descDefinitivo = d;
        }
        
        return descDefinitivo;
    }

}

interface  AlgoritmoDescuento {

    public boolean comprobarProductoValidoDescuento(Producto p);
}
Y Aquí un pequeño ejemplo de cómo se trabajaría con estos Objetos:
public class Enums {

    public static void main(String[] args) {

        // Su descuento será el Descuento para juguetes ya que solo cumple ese
        procesarCompra(new Producto("Juguete1", 30, Tipo.JUGUETES));
        // Su descuento será entre 100 y 200 
        // por que este descuento es mayor que el de juguete
        procesarCompra(new Producto("Juguete2", 150, Tipo.JUGUETES));
        // Su descuento será MAYOR200 por que ese descuento es mayor que el de juguete
        procesarCompra(new Producto("Juguete3", 220, Tipo.JUGUETES));
        // Su descuento será entre 100 y 200
        procesarCompra(new Producto("Hogar1", 150, Tipo.HOGAR));
        // Su descuento será mayor de 200
        procesarCompra(new Producto("Hogar2", 220, Tipo.HOGAR));
        // Su descuento será no descuento
        procesarCompra(new Producto("Hogar3", 20, Tipo.HOGAR));
    }

    private static void procesarCompra(Producto p) {
        
        Descuentos d = Descuentos.comprobarTotalDescuentosProducto(p);
        float aDescontar = p.getPrecio() * d.getDescuento() / 100;
        System.out.println("Producto:  " + p.getNombre() + "\n"
                + "==============================================\n"
                + "Precio sin descuento:  " + p.getPrecio() + "\n"
                + "Descuento aplicado:  " + aDescontar + "\n"
                + "Tipo de Descuento aplicado:  \n\t"
                + d.getDescripcion() + "\n"
                + "----------------------------------------------\n"
                + "Precio tras descuentos: " + (p.getPrecio() - aDescontar) + "\n"
                + "==============================================\n"
                + "Total: " + ((p.getPrecio() - aDescontar) * 1.21) + "\n");
    }
}
    Como se puede ver, los Enum en Java son muy potentes, Ahora hablaré del Patrón Estrategia que he usado con ellos. El Patrón estrategia se basa en escribir el programa de manera que hasta tiempo de ejecución no se decida que algoritmo usar. En este caso, se hace con el algoritmo de los descuentos. Para ello creamos una Interface funcional, que tenga el método que se busca, en este caso:
interface  AlgoritmoDescuento {

    public boolean comprobarProductoValidoDescuento(Producto p);
}
    Y luego simplemente se crean clases que implementen esta interfaz, ya sean anónimas o no, y se define el uso que se le va a dar. En este programa, el uso que se le da es en el bucle del método Descuentos. comprobarTotalDescuentosProducto(Producto p), que ejecuta el método de cada Algoritmo, sin saber hasta tiempo de ejecución cuál de todos será. Planteando el programa de esta manera, podremos ampliarlo simplemente añadiendo una nueva constante al Enum, con su algoritmo para comprobar los productos, y no habría que tocar el código de ninguna clase, puesto que, usando el Patrón Estrategia, nos hemos asegurado de que el programa sepa usar la interface que el Algoritmo implementará.

No hay comentarios:

Publicar un comentario