Los enums siguen molando

Escribí hace tiempo una entrada en la que hacía público mi amor hacia los enums. A día de hoy creo que no los uso lo suficiente y voy poco a poco aprendiendo cosas nuevas. Con todo esto, no me ha quedado otra que escribir una segunda parte: Los enums siguen molando.

Una característica por la que los enums son conocidos y se suelen usar es la imposibilidad de invocar al constructor (ni con reflection) por lo que son buenos candidatos para utilizarlos como singletons.

En Effective Java, el autor define los métodos constant-specific methods como las implementaciones de un método abstracto en el enum. Esto permite asociar comportamiento a los elementos que se definen. Y obliga a definir el comportamiento por cada elemento nuevo.

public enum PiecesToClean {
    LIVING_ROOM {
        @Override public void clean() {
            System.out.println("Cleaning the living room");
        }
    },
    KITCHEN {
        @Override public void clean() {
            System.out.println("Cleaning the kitchen");
        }
    },
    BATH_ROOM {
        @Override public void clean() {
            System.out.println("Cleaning the bath room");
        }
    };

    public abstract void clean();
}

Por otra parte, Joshua Bloch propone el strategy enum pattern en el que los diferentes comportamientos de los enums son definidos en el constructor de cada elemento. De esta forma, el compilador nos obliga a definir el comportamiento para cada constante pero además contamos con la flexibilidad del patrón strategy a la hora de escribir diferentes comportamientos reutilizables para cada enum.

public enum PiecesToClean {
    LIVING_ROOM(CleaningStrategy.SHALLOW),
    KITCHEN(CleaningStrategy.DEEP),
    BATH_ROOM(CleaningStrategy.DEEP);

    private final CleaningStrategy cleaningStrategy;

    PiecesToClean(CleaningStrategy cleaningStrategy) {
        this.cleaningStrategy = cleaningStrategy;
    }

    public void clean() {
        cleaningStrategy.clean();
    }
}
public enum CleaningStrategy {
    SHALLOW() {
        @Override public void clean() {
            System.out.println("Cleaning fast");
        }
    },
    DEEP() {
        @Override public void clean() {
            System.out.println("Cleaning with love");
        }
    };

    public abstract void clean();
}

En el mismo capítulo también se propone lanzar una excepción para el caso por defecto en los bloques switch sobre el enum ya que el autor pone en evidencia el peligro de los enums en bloques switch teniendo en cuenta que se puede añadir un elemento nuevo pero olvidar añadir en los bloques switch (diseminados por todo el código) el caso para esa constante, por lo que aplicará el caso por defecto.

switch (piece) {
   case LIVING_ROOM:
      piece.clean(); break;
   case KITCHEN:
      piece.clean(); break;
   case BATH_ROOM:
      piece.clean(); break;
   default:
      throw new IllegalArgumentException("You should have defined this use case");
}

Mi compi Jorge me enseñó el otro día cómo utilizaba un enum para implementar un Predicate de Java 8. Cada elemento del enum es una regla de negocio y cada uno implementa test() para determinar si se cumple la regla.

public enum BusinessRule implements Predicate {
    RULE_1() {
        @Override public boolean test(DomainObjectUnderRules domainObjectUnderRules) {
            // logic to test rule 1
        }
    },
    RULE_2 {
        @Override public boolean test(DomainObjectUnderRules domainObjectUnderRules) {
            // logic to test rule 2
        }
    };
}

A parte de impresionarme esta forma de usar enums entendí que Java 8 está facilitando mucho el uso de los enumerados, sobre todo, en los casos en los que el enum recibe una lambda en el constructor.

Para terminar, en el libro se recuerda que en Java 7 se introdujo EnumMap que es una implementación de mapas especialmente desarrollada para enums, muy compacta y muy eficiente y cuyo uso no es muy extendido.

 

Lecciones de diseño desarrollando Puppet

Dicen que es muy bueno leer código de otros. Un paso intermedio es leer cómo otros han diseñado ese código y para esto hay un libro espectacular: The Architecture Of Open Source Applications en el que desarrolladores cuentan entre otras cosas cómo han evolucionado arquitecturas y software que diariamente usamos como Git, Puppet, MediaWiki o Hadoop.

Leyendo el capítulo de Puppet se puede uno encontrar cómo este framework ha ido progresando con los principios de diseño de buen software muy en mente tales como bajo acoplamiento, alta cohesión, inyección de dependencias…

Declararon los recursos (archivos, programas…) como primitivos del framework y definieron una capa Resource Abstraction Layer desde la que usar los recursos independientemente del sistema operativo o la implementación de su uso. Se esforzaron por darle la posibilidad al usuario de Puppet de enfocarse en lo que necesita más que en cómo lograrlo.

Desarrollaron los clientes Puppet sin dejarle acceso a los módulos (encapsulamiento) entregándoles su configuración correspondiente. Así, cada cliente sólo conoce su configuración sin conocer la configuración de otros servidores evitando de esta manera que un cliente cree dependencias en servidores de los que no debería depender.

Más adelante en el texto comentan que el nodo Master Puppet recibe las necesidades de un nodo y más que configurarlo lo clasifica, cosa que me recuerda mucho a la orientación a objetos: Crear varias clases con la misma interfaz pero diferente comportamiento en la implementación, en lugar de tener sólo una clase y un montón de condiciones para definir el flujo dependiendo de la configuración.

Desarrollando un software para gestionar recursos en diferentes máquinas, se encontraron con la misma problemática de gestionar recursos en el código y poco a poco fueron desarrollando su propio framework (dentro de Puppet) de inyección de dependencias llamado Indirector que con el paso de las versiones ha llegado a gestionar todas las dependencias de la aplicación completa.

Y para terminar el texto concluye dándonos una gran lección sobre desarrollo de software:

Lastly, it took us too long to realize that our goals of simplicity were best expressed in the language of design. Once we began speaking about design rather than just simplicity, we acquired a much better framework for making decisions about adding or removing features, with a better means of communicating the reasoning behind those decisions.