Les membres publiques, statiques et finaux sont souvents employes pour fournir des instances pre-definies d'une classe. La classe java.awt.Color est un excellent exemple de cette technique : Color.RED, Color.BLUE, etc. sont des instances de Color avec des composants RVB pre-definies.
Cette technique est excellente et servait jusqu'a Java 1.5 a definir des enumerations. Elle souffre neanmoins d'un grave defaut lie a la mutabilite des objets. Si vous declarez de telles instances a partir d'une classe non-immuable, vous vous reservez de longues sessions de debugging hardu. (Plus d'informations sur les classes immuables.) Le concept de classe immuable etant souvent mal maitrise, la technique des membres publiques, statiques et finaux l'est egalement.
Prenons un exemple tres simple :
public class Name {
public static final Name FENX = new Name("fenx");
public static final Name JOJOLAPIN = new Name("jojolapin");
private String name;
public Name(String name) {
this.name = name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return this.name;
}
}
Je suis persuade que vous avez deja vu de nombreuses classes ainsi redigees. Il se peut meme que vous soyez le forban responsable d'une telle maladresse ! Nos isntances constantes sont tres fragiles :
Name fenx = Name.FENX;
fenx.setName("Pifi");
System.out.println(Name.FENX);
Je vous epargne la copie du resultat depuis ma console car vous devez avoir a present compris que FenX est devenu Pifi. Une technique souvent utilisee pour pallier ce probleme est l'utilisation d'une interface.
Les exemples suivants reposent sur de vraies classes du projet SwingX. Richard Bair et moi-meme avons recemment introduit l'API des painters (demo) et nous souhaitons proposer des painters pre-construits et utilisables tels quels. Un painter est definit par l'interface org.jdesktop.swingx.painter.Painter qui definit une seule et unique methode, paint(Graphics2D, JComponent). En pratique tous nos painters heritent de la classe abstraite AbstractPainter qui fournit une API assez complete pour gerer la qualite, le clipping, un cache, etc.
Pour implementer nos painters par defaut, notre premier reflexe a ete de declarer des constantes publiques :
public static final Painter GLOSSY_STRIPES = new CompoundPainter(
new PinstripePainter(), new GlossPainter());
Cette implementation semble parfaitement sure a premiere vue car l'interface Painter ne permet pas de muter les instances. C'est bien mal connaitre notre volonte de trouver des utilisations tordues. Nous avons heureusement immediatement identifie un grave probleme :
((CompoundPainter) Painter.GLOSSY_STRIPES).setPainters(
new MattePainter(Color.RED));
Apres execution de cette ligne, GLOSSY_STRIPES ne dessine plus un reflet sur un fond de lignes obliques, mais un simple aplat de couleur rouge. La puissance du transtypage est sans limite. Comment prevenir une telle situation ? En utilisant le design pattern decorateur et la notion de composition si chere a la POO :
class ImmutablePainter implements Painter {
private final Painter painter;
ImmutablePainter(final Painter painter) {
if (painter == null) {
throw new IllegalArgumentException("Null painter.");
}
this.painter = painter;
}
public void paint(Graphics2D g2, JComponent c) {
this.painter.paint(g2, c);
}
}
// ...
public static final Painter GLOSSY_STRIPES = new ImmutablePainter(
new CompoundPainter(new PinstripePainter(), new GlossPainter()));
ImmutablePainter n'est pas reellement immuable car le client creant l'instance conserve une reference sur le painter passe en parametre et peut le modifier plus tard. Neanmoins, notez que cette classe est package-private, donc accessible uniquement par notre API. Cela signifie que nous pouvons garantir, par contrat, l'immuabilite de nos instances. Notez egalement la redefinition de la constante GLOSSY_STRIPES. Celle-ci ne peut plus etre convertie en une implementation de Painter car ImmutablePainter est inaccessible. Nos instances sont maintenant en surete.
Notez que cette technique est egalement indispensable si vous utilisez une fabrique a la place de constantes publiques. Le cas de la fabrique est neanmoins un peu different car vous pouvez retourner une copie de vos instances. Mais cela signifique que vos classes doivent supporter l'interface Cloneable, qui apporte son propre lot d'ennuis. Quoi qu'il en soit, ces exemples mettent en evidence l'interet des classes immuables.
Cette news a été rédigée en écoutant Rob D - Clubbed To Death