I. Introduction▲
Les membres publics, statiques et finaux sont souvent employés pour fournir des instances prédéfinies 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 composantes RVB prédéfinies.
Cette technique est excellente et servait jusqu'à Java 1.5 à définir des énumérations. Elle souffre néanmoins d'un grave défaut lié à la mutabilité des objets. Si vous déclarez de telles instances à partir d'une classe non immuable, vous vous réservez de longues sessions de débogage ardu. (Plus d'informations sur les classes immuables) Le concept de classe immuable étant souvent mal maîtrisé, la technique des membres publics, statiques et finaux l'est également.
Prenons un exemple très 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 persuadé que vous avez déjà vu de nombreuses classes ainsi rédigées. Il se peut même que vous soyez le forban responsable d'une telle maladresse ! Nos instances constantes sont très fragiles :
Name fenx =
Name.FENX;
fenx.setName
(
"Pifi"
);
System.out.println
(
Name.FENX);
Je vous épargne la copie du résultat depuis ma console, car vous devez avoir à présent compris que FenX est devenu Pifi. Une technique souvent utilisée pour pallier ce problème est l'utilisation d'une interface.
Les exemples suivants reposent sur de vraies classes du projet SwingX. Richard Bair et moi-même avons récemment introduit l'API des painters (demo) et nous souhaitons proposer des painters préconstruits et utilisables tels quels. Un painter est défini par l'interface org.jdesktop.swingx.painter.Painter qui définit une seule et unique méthode, paint(Graphics2D, JComponent). En pratique tous nos painters héritent de la classe abstraite AbstractPainter qui fournit une API assez complète pour gérer la qualité, le clipping, un cache, etc.
Pour implémenter nos painters par défaut, notre premier réflexe a été de déclarer des constantes publiques :
public
static
final
Painter GLOSSY_STRIPES =
new
CompoundPainter
(
new
PinstripePainter
(
), new
GlossPainter
(
));
Cette implémentation semble parfaitement sûre à première vue, car l'interface Painter ne permet pas de muter les instances. C'est bien mal connaître notre volonté de trouver des utilisations tordues. Nous avons heureusement immédiatement identifié un grave problème :
((
CompoundPainter) Painter.GLOSSY_STRIPES).setPainters
(
new
MattePainter
(
Color.RED));
Après exécution 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 limites. Comment prévenir une telle situation ? En utilisant le design pattern décorateur et la notion de composition si chère 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 réellement immuable, car le client créant l'instance conserve une référence sur le painter passé en paramètre et peut le modifier plus tard. Néanmoins, notez que cette classe est package-private, donc accessible uniquement par notre API. Cela signifie que nous pouvons garantir, par contrat, l'immuabilité de nos instances. Notez également la redéfinition de la constante GLOSSY_STRIPES. Celle-ci ne peut plus être convertie en une implémentation de Painter, car ImmutablePainter est inaccessible. Nos instances sont maintenant en sûreté.
Notez que cette technique est également indispensable si vous utilisez une fabrique à la place de constantes publiques. Le cas de la fabrique est néanmoins un peu différent, car vous pouvez retourner une copie de vos instances. Mais cela signifie que vos classes doivent supporter l'interface Cloneable, qui apporte son propre lot d'ennuis. Quoi qu'il en soit, ces exemples mettent en évidence l'intérêt des classes immuables.