septembre
2009
Chez les développeurs débutants, on peut observer deux attitudes opposées :
- Soit ils utilisent beaucoup trop de variables publiques
- Soit ils suivent aveuglément les conseils et utilisent des getters partout, même dans la classe d’origine.
Selon moi, ces deux cas extrêmes illustrent qu’ils n’ont pas compris l’intérêt du principe d’encapsulation des données.
Je me propose d’en faire le sujet de ce billet qui je l’espère sera propice à quelques réflexions.
=> Le principe d’encapsulation
La première fois que j’ai du l’expliquer, j’ai du avoir cette conversation :
C’est quoi ?
Le principe d’encapsulation consiste à ce qu’une classe cliente n’accède pas directement aux attributs d’une autre classe.
Elle y accède par l’intermédiaire de méthodes de lecture (accesseur/getter) et/ou d’écriture (mutateur/setter).
A quoi ça sert ?
Cela permet :
- d’autoriser ou non l’accès en lecture/écriture à un attribut.
Ce point est le plus clair.
Si un attribut est publique, on peut aussi bien le lire que le modifier car en Java, il n’existe pas de mots clés readable-only, writable-only …
Si on ne veut autoriser que la lecture ou que l’écriture, déclarer uniquement un getter ou un setter paraît donc une bonne solution. - d’affaiblir le couplage entre les classes (la classe expose ce qu’elle fait et non plus comment elle le fait).
Ce point est le plus abstrait et à cause des nombreux exemples bateaux, les novices n’en perçoivent pas immédiatement l’intérêt.
Dès qu’une classe A utilise une classe B, on peut dire qu’elles sont couplées.
Si A manipule directement les attributs de B ou appelle les méthodes de B, toutes modifications des attributs et méthodes de B impacte donc A ainsi que toutes les autres classes clientes de B.
En encapsulant les données de B (en cachant ses attributs à l’extérieur), on affaiblit le couplage.
A n’est donc désormais sensible qu’aux méthodes de B qu’elle utilise mais contrairement aux attributs, le couplage par les méthodes a un avantage : les méthodes exposent leur signature et pas leur implémentation. Il suffit que la classe fournisse toujours les mêmes signatures de méthodes, libre à elle ensuite de modifier le code.
=> Dans ma classe, dois-je accéder aux attributs en passant par leurs accesseurs ?
- L’accesseur ne fait que retourner la valeur de l’attribut
Dans ce cas, autant utiliser directement l’attribut. - L’accesseur fait autre chose
- Il y a du code spécifique pour les clients externes et dans ce cas (et c’est ce que disait zedros dans son commentaire) la classe a tout intérêt à manipuler directement son attribut.
- Le code n’est pas spécifique aux clients externes mais est répétitif. Utiliser l’accesseur peut éviter des copier/coller.
=> Dans les classes filles, dois-je accéder aux attributs en passant par leurs accesseurs ?
Là, je pense que ça dépend des situations et je n’ai pas encore d’avis tranché. Si vous en avez, n’hésitez pas à les exprimer.
- Les classes mère/fille font parties toutes les deux de mon code.
Les remarques précédentes peuvent probablement s’appliquer. - Je code une classe destinée à être étendue par d’autres. Cela revient à se demander comment choisir la visibilité d’un attribut (private/protected) mais j’y reviendrai dans un autre billet.
- J’étends une classe que je ne maîtrise pas …
Ne conseille t’on pas dans le cas général de préférer la composition à l’héritage ?
=> L’appel du solde : Réglons nos comptes !
J’ai nommé ce billet « L’ennemi vient de l’intérieur » pour illustrer deux phénomènes :
-
Le premier, c’est pour montrer que sans le principe d’encapsulation, les classes clientes (externes) qui appelleraient directement les attributs d’une autre classe seraient impactée par les modifications internes de cette classe.
Un bon exemple est celui de la caractéristique « solde » d’un objet Compte, tantôt stocké sous forme d’attribut, tantôt calculé lors de l’accès.
Accéder à la caractéristique solde via getSolde() nous met à l’abri de son implémentation (par stockage/par calcul) -
Le deuxième, qui découle du précédent, c’est qu’il faut être prudent dans l’appel des getters.
Habitué à ce que le getter soit généralement trivial, je vois souvent des appels d’accesseurs en cascade au sein d’une même portion de code :if (compte.getSolde() > 0)
{
System.out.println("Avant conversion : " + compte.getSolde());
System.out.println("Après conversion : " + convert(compte.getSolde()));
}Si on voulait se servir d’un tel code, il serait bon de s’assurer :
- que la méthode ne retourne pas de null
- que la méthode n’est pas trop coûteuse en temps
Pour cela,
- La meilleure assurance, c’est d’aller voir dans le code source mais il n’est pas toujours disponible et même s’il l’était, c’est contraire au principe d’encapsulation !
- Une autre serait de consulter les commentaires de mise en garde s’ils sont présent (ce qui n’est pas certain) et de leur faire confiance !
- La dernière enfin, c’est d’être sur la défensive face à l’inconnu (pas de commentaire, code non accessible) :
Integer solde = compte.getSolde();
if (solde != null && solde > 0)
{
System.out.println("Avant conversion : " + solde);
System.out.println("Après conversion : " + convert(solde));
}
Et vous, avez vous vos propres habitudes sur cette question ?
+1 avec les deux autres.
Personnellement, j’essaie de créer le moins possible de getters/setters et de passer plutôt par des fonctions métiers qui sont souvent mieux adaptées, mais je ne suis pas non plus de ceux qui font tout sans getters/setters, au contraire.
Bonne remarque en effet …
S’il est souhaitable de passer par des getters/setters pour les attributs public, il ne faut pas tomber dans l’excès inverse et définir des getter/setters pour tous les attributs.
Ceux qui relève de la cuisine interne ne doivent pas en avoir bien évidemment.
Personnellement (mais bon, en C#), tous les membres qui doivent être visibles de l’extérieur sont accédés par des getters/setters, certaines facilites du framework n’étant disponibles que si on expose des propriétés (le petit nom des getters/setters en .Net)
Après, pour des cas métiers, on passe en général par une fonction.
Par contre, pas mal de membres ne sont pas exposés sous forme de propriété, mais sont purement privée, et réservés a la cuisine interne de l’objet.
Exposer tous les membres internes sous forme de getter/setter, ce n’est plus de l’encapsulation, au contraire, c’est exposer la structure interne de la classe