La problématique

Le but de ce billet est d’implémenter un besoin très classique : plusieurs types avec un ensemble de valeurs finies, accessibles à partir d’un code. Cela permet de représenter des statuts, des états, …

La solution

Préambule : cet article fait référence à l’excellent livre de Joshua Bloch : Effective Java, 2nd Edition.

Pour chaque type, un Enum est créé (cf Item 30 : Use enums instead of int constants) avec une méthode servant de factory static (cf Item 1 : Consider static factory methods instead of constructors). Afin de respecter le principe DRY, nous allons mettre en place un mécanisme afin de ne pas dupliquer le code de cette méthode dans chaque type. Premièrement, une interface Reference est créée (cf Item 34 : Emulate extensible enums with interfaces). Ensuite, une classe utilitaire References est créée afin de servir de dictionnaire; c’est-à-dire pour stocker toutes les valeurs de tous les types (cf Item 29 : Consider heterogeneous containers).

Reference

public interface Reference {
    public int getCode();
}

References

public class References {
    private static final Map<String, Map<Integer, Reference>> DICTIONARIES;

    static {
        DICTIONARIES = new HashMap<String, Map<Integer, Reference>>();
    }

    public static <T extends Enum<T> & Reference> void register(Class<T> clazz) {
        Map<Integer, Reference> dictionary = new HashMap<Integer, Reference>();
        for (Reference ref : clazz.getEnumConstants()) {
            dictionary.put(ref.getCode(), ref);
        }
        DICTIONARIES.put(clazz.getName(), dictionary);
    }

    public static <T extends Enum<T> & Reference> T fromCode(Class<T> clazz, int code) {
        return clazz.cast(DICTIONARIES.get(clazz.getName()).get(code));
    }

}

ExampleReference

public enum ExampleReference implements Reference {
    FOO(1),

    BAR(2);

    private final int code;

    private ExampleReference(int code) {
        this.code = code;
    }

    public int getCode() {
        return code;
    }

    static {
        References.register(ExampleReference.class);
    }

    public static ExampleReference fromCode(int code) {
        return References.fromCode(ExampleReference.class, code);
    }

    public static void main(String[] args) {
        System.out.println(ExampleReference.fromCode(1));
    }

}

Cette implémentation présente les limites suivantes :

  • Un enum ne pouvant pas étendre une classe, l’attribut code ainsi que le constructeur et le getter associé sont dupliqués dans chaque enum
  • Chaque objet doit déclarer un constructeur statique

Le lecteur remarquera au passage l’incroyable complexité des génériques, en particulier cette syntaxe indigeste : <T extends Enum<T> & Reference>.

N’hésitez pas à me contacter si vous avez des suggestions d’améliorations.