Créer des instances à travers un enum

Le plus souvent un enum sert à reprendre une liste de constantes que l’on peut notamment utiliser de manière sémantique lors du passage de paramètres (ex: Color.white). Mais en Java il existe d’autres possibilités dont une manière pratique pour créer des instances que je vais décrire dans cet article.

L’objectif de l’exercice sera de générer une liste de Filefilter qui pourrons être ajoutés à un JFileChooser (composante de la bibliothèque Swing). En comprenant l’approche vous devriez pouvoir l’adapter à vos besoins.

Tout d’abord on crée un enum de base qu’on appellera ImageFileFiltersEnum. Notez l’utilisation d’un String array en paramètres qui pourra servir avec la classe décrite dans l’article précédent.

public enum ImageFileFiltersEnum {
   
  JPEG("JPEG files", new String[]{".jpg",".jpe",".jpeg",".jfif",".fif"} ),
  BITMAP("Bitmap files", new String[]{".bmp"} ),
  PNG("PNG files", new String[]{".png"} ),
  GIF("GIF files", new String[]{".gif"} ),
  TIFF("TIFF files", new String[]{".tif"} ),

On ajoute ensuite des attributs pour accomoder les valeurs d’une énumération et un constructeur privé qui servira pour les initialiser.

  private String filterName;
  private String[] fileNameExtensions;
 
  private FileFiltersEnum(String filterName, String[] fileNameExtensions ) {
    this.filterName = filterName;
    this.fileNameExtensions = fileNameExtensions;
  }

Pour finir voici des méthodes getter. La première va servir pour renvoyer un seul FileFilter lorsqu’une seule énumération est référée et la deuxième va renvoyer toutes les énumérations définies.

  public FileFilter getFileFilter() {  
    return new MultipleExtensionFileFilter(filterName,fileNameExtensions);
  }
 
  public static List<FileFilter> getAllFileFilters() {
   
    List<FileFilter> fileFilters = new ArrayList<FileFilter>();
   
    for( FileFiltersEnum fileFilterEnum : FileFiltersEnum.values()) {
      fileFilters.add(new MultipleExtensionFileFilter(fileFilterEnum.filterName,fileFilterEnum.fileNameExtensions));
    }
   
    return fileFilters;
  }

L’invocation des méthodes se fait de la manière suivante.

chooser = new JFileChooser(); // On crée un JFileChooser
 
FileFilter jpegFileFilter = ImageFileFiltersEnum.JPEG.getFileFilter();  // On récupère un seul Filter
 
chooser.addChoosableFileFilter(jpegFileFilter);
 
// On récupère tous les Filefilters
List<FileFilter> imageFilters = ImageFileFiltersEnum.getAllFileFilters();
 
for ( FileFilter filter : imageFilters ) {
    chooser.addChoosableFileFilter(filter);
}

Voici qui termine une approche qui pourrait servir d’alternative à l’utilisation de simples constantes. Avec un peu d’imagination on pourrait l’utiliser de sorte à rendre des pseudo-constantes indépendantes de l’interface utilisée.

– James Poulson.

20 réflexions au sujet de « Créer des instances à travers un enum »

  1. Ping : Recap java, semaine 29, année 2012 | Blog de la rubrique java

  2. Perso, j’aime bien utiliser le côté objet des enum Java pour les enrichir avec des caractéristiques supplémentaires.

    Après, c’est sûr, il faut faire attention de ne pas trop en faire.
    Pour l’exemple, j’aime bien la factory de « le y@m’s » mais dans l’enum FileType, je laisserai le libellé et les extensions (par défaut) associés au type de fichier. Je trouve que ces infos ont leur place ici.

  3. La cohésion est une notion de POO qui en quelques sorte représente les responsabilités d’une classe.
    Une forte cohésion indique qu’une classe ne se disperse pas dans une multitude de responsabilité, elle se « concentre » sur une chose. A l’inverse, une faible cohésion indique une classe faisant plusieurs choses différentes.

    Du fait quelle rend une classe plus simple, une forte cohésion augmente sa lisibilité et sa compréhension. Elle augmente également la maintenabilité ainsi que la modularité du code.

    C’est comme le principe qu’il est plus facile de résoudre plusieurs petit problèmes simples qu’un seul gros problème compliqué ;).

    C’est un concept important de la POO tout comme le couplage (qui lui, doit au contraire être le plus faible possible). En cherchant sur le net tu trouveras facilement de la littérature sur ces deux notions ;)

  4. Merci pour les commentaires. J’avoue ici avoir visé le possible sans me pencher sur la conception et les aspects pratiques. L’idée est juste venu en voyant que certains programmeurs décrivent des valeurs par défaut en « dur » pour substituer l’éventuelle absence d’un fichier de configuration.

    @le y@m’s, j’apprécie beaucoup. Pourrais-tu expliquer ton raisonnement face à cette question de cohésion ? S’agit t-il de diviser les données et responsabilités en unités distinctes ? Je comprend le principe du motif Factory.

    @lunatix, il est vrai que l’Enum peut être utilisé à d’autres fins. Pour le singleton cela est justement conseillé par Jon Skeet sur le site StackOverflow. C’est dans cet esprit là que j’ai imaginé la structure ci-dessous.

    @Tarul: Tu as tout à fait raison! Je n’avais pas pensé à cela. En effet, lorsqu’on appelle un enum on dirait que toutes les valeurs sont enclenchées à la fois. Je réflêchirait à un moyen d’éviter cela mais la solution du Factory semble être plus appropriée.

  5. Bonjour,

    je ne vais pas critiquer la mise en place d’une factory dans une enum (en fait je comprend que l’on souhaite mettre ce genre de chose dans certains cas), mais je vais quand même faire une critique sur le code de l’article.

    Les méthodes suivantes :

     public FileFilter getFileFilter() {  &nbsp;<br />
    &nbsp;   return new MultipleExtensionFileFilter(filterName,fileNameExtensions); &nbsp;<br />
    &nbsp; } &nbsp;<br />
    &nbsp; &nbsp;<br />
    &nbsp; public static List getAllFileFilters() { &nbsp;<br />
    &nbsp;   &nbsp;<br />
    &nbsp;   List fileFilters = new ArrayList(); &nbsp;<br />
    &nbsp;   &nbsp;<br />
    &nbsp;   for( FileFiltersEnum fileFilterEnum : FileFiltersEnum.values()) { &nbsp;<br />
    &nbsp;     fileFilters.add(new MultipleExtensionFileFilter(fileFilterEnum.filterName,fileFilterEnum.fileNameExtensions)); &nbsp;<br />
    &nbsp;   } &nbsp;<br />
    &nbsp;   &nbsp;<br />
    &nbsp;   return fileFilters; &nbsp;<br />
    &nbsp; }&nbsp;<br />

    ont le défaut de générer de nouvelles instances de MultipleExtensionFileFilter à chaque appel. Ce n’est pas ça qui va te provoquer
    un OutOfMemory, mais je trouve cela dommage.

  6. Je ne suis pas un extremiste du « non au singleton » mais c’est un dp à utiliser avec extrême modération. Dans la très grande majorité des cas une interface et une injection de dépendance sont à préférer.

    Rajouter les responsabilités dans une classe comme ça va ne fait que réduire sa cohérence, la rendre moins flexible, moins lisible et moins maintenable. Quel est l’intérêt de faire ça au vu des inconvénients ? Avoir une seule classe au lieu de trois ? La belle affaire ^^.

    Alors oui l’enum est un objet et peut servir à faire des factory, mais ce qu’on peut faire n’est pas toujours la meilleure chose à faire ;) (ce n’est pas parce qu’on peut que c’est bien, on peut faire beaucoup d’horreur en Java ^^)

  7. « Une enum est une enum, son principe est de définir une liste fixe de constante point. »

    ben non, justement, l’Enum java est un objet, et il peut servir comme factory. (c’est même le meilleur moyen de faire des singletons par exemple)

    c’est pas l’enum C++

  8. Je trouve que c’est un peu crado de mélanger les concepts comme ça :s et de faire d’une enum une pseudo factory. C’est pas flexible, pas extensible, la cohésion en prend un sacré coup et ça nuit aussi à la lisibilité et à la maintenabilité du code.

    Il est préférable de séparer les aspects et les responsabilités. Une enum est une enum, son principe est de définir une liste fixe de constante point.

    Si on veut associer quelque chose à ces constante (tes FileFilter par exemple) ce n’est pas à l’enum d’en prendre la responsabilité. Ces enum pourraient servir dans un autre contexte où le concept de FileFilter n’a aucun intérêt. Et si dans cet autre context tu as besoin d’associer autre chose à tes enum, tu vas encore modifier la classe de l’enum ? En faisant ça on arrive vite à des classes monstres et monstrueuses qui font tout et n’importe quoi. Après c’est l’horreur à maintenir.

    Donc pour associer tes enum à tes FileFilter il est préférable de faire une simple factory et de laisser ton enum la plus simple possible.

    &nbsp;<br />
    // enumeration simple&nbsp;<br />
    public enum FileType {&nbsp;<br />
    &nbsp;   JPEG,&nbsp;<br />
    &nbsp;   BITMAP,&nbsp;<br />
    &nbsp;   PNG,&nbsp;<br />
    &nbsp;   GIF,&nbsp;<br />
    &nbsp;   TIFF&nbsp;<br />
    }&nbsp;<br />
    &nbsp;<br />
    // interface de ma factory&nbsp;<br />
    public interface MultipleExtensionFileFilterProvider {&nbsp;<br />
    &nbsp;<br />
    &nbsp;   MultipleExtensionFileFilter getFileFilter(FileType fileType);&nbsp;<br />
    &nbsp;<br />
    &nbsp;   MultipleExtensionFileFilter[] getAllFileFilters();&nbsp;<br />
    }&nbsp;<br />
    &nbsp;<br />
    // une implementation&nbsp;<br />
    public class DefaultMultipleExtensionFileFilterProvider implements MultipleExtensionFileFilterProvider {&nbsp;<br />
    &nbsp;<br />
    &nbsp;   private Map filters = new EnumMap(FileType.class);&nbsp;<br />
    &nbsp;<br />
    &nbsp;   public DefaultMultipleExtensionFileFilterProvider() {&nbsp;<br />
    &nbsp;       filters.put(FileType.JPEG, new MultipleExtensionFileFilter("JPEG files", ".jpg",".jpe",".jpeg",".jfif",".fif"));&nbsp;<br />
    &nbsp;       filters.put(FileType.BITMAP, new MultipleExtensionFileFilter("Bitmap files", ".bmp"));&nbsp;<br />
    &nbsp;       filters.put(FileType.PNG, new MultipleExtensionFileFilter("PNG files", ".png"));&nbsp;<br />
    &nbsp;       filters.put(FileType.GIF, new MultipleExtensionFileFilter("GIF files", ".gif"));&nbsp;<br />
    &nbsp;       filters.put(FileType.TIFF, new MultipleExtensionFileFilter("TIFF files", ".tif"));&nbsp;<br />
    &nbsp;   }&nbsp;<br />
    &nbsp;   &nbsp;<br />
    &nbsp;   @Override&nbsp;<br />
    &nbsp;   public MultipleExtensionFileFilter getFileFilter(FileType fileType) {&nbsp;<br />
    &nbsp;       return filters.get(fileType);&nbsp;<br />
    &nbsp;   }&nbsp;<br />
    &nbsp;   &nbsp;<br />
    &nbsp;   @Override&nbsp;<br />
    &nbsp;   public MultipleExtensionFileFilter[] getAllFileFilters() {&nbsp;<br />
    &nbsp;       return filters.values().toArray(new MultipleExtensionFileFilter[filters.size()]);&nbsp;<br />
    &nbsp;   }&nbsp;<br />
    &nbsp;   &nbsp;<br />
    }&nbsp;<br />

    Ainsi pour l’utilisation on utilise la factory de manière transparente

    &nbsp;<br />
    MultipleExtensionFileFilterProvider filterProvider = new DefaultMultipleExtensionFileFilterProvider();&nbsp;<br />
    &nbsp;<br />
    ...&nbsp;<br />
    FileFilter jpegFileFilter = filterProvider.getFileFilter(FileType.JPEG);&nbsp;<br />
    chooser.addChoosableFileFilter(jpegFileFilter); &nbsp;<br />
    &nbsp;<br />
    // On récupère tous les Filefilters &nbsp;<br />
    List imageFilters = filterProvider.getAllFileFilters(); &nbsp;<br />
    &nbsp;<br />
    for ( FileFilter filter : imageFilters ) { &nbsp;<br />
    &nbsp;   chooser.addChoosableFileFilter(filter); &nbsp;<br />
    } &nbsp;<br />

    L’autre avantage est que tu peux utiliser une autre implémentation de la factory, tes associations ne sont pas gravées dans le marbre ad vitam aeternam.

Les commentaires sont fermés.