Passer au contenu principal

L’astuce évoquée cette fois va sembler une évidence pour certains, et sera totalement nouvelle pour d’autres. Et pourtant elle est importante et à la base de tout langage objet voire non objet comme le C.

Les exemples ci-dessous seront donnés pour du Java car c’est ce que je maîtrise le mieux, mais ils sont parfaitement applicables à d’autres langages, objet ou pas.

Instanciation d’objets dans des fonctions ou en variables membre des objets

On trouve régulièrement des codes du type suivant :

ArrayList<String> l = new ArrayList<>();

Oui pour les deux du fond qui ne suivent pas ce code ne compilera qu’à partir de Java 7.

Bref ce code est mauvais car ici on déclare l’objet l en tant qu’instance d’ArrayList et non de List. Mais alors, où est le problème ? En fait c’est simple : si un jour on décide de passer à une LinkedList on sera obligé de repasser dans tout le code pour changer la déclaration de type de ArrayList vers LinkedList. Assez pénible.

Maintenant si au contraire on avait défini l comme List, il suffisait de changer la ligne où on l’instancie et c’était réglé.

Dans une fonction, ça ne paraît pas très gênant, maintenant dans les membres d’une classe c’est bien plus gênant. Mettons qu’on ait la déclaration de variable suivante :

private ArrayList<String> myList;

Dans le cas où myList est instanciée dans plusieurs endroits du code de votre classe, il faudra changer les instanciations de votre objet <code<myList partout dans le code. Là encore c’est assez compliqué à maintenir, mais bon ça ne paraît pas encore bien grave, vous me direz, hormis que c’est moche. La solution là encore est d’écrire :

private List<String> myList;

Mais bon, il y a bien pire, dans la suite.

Les déclarations de type dans les méthodes et fonctions

Là, on arrive au coeur du problème : les déclarations des types dans les signatures de méthodes. Soit la méthode suivante :

public HashSet<String> doSomething(final ArrayList<String> l)

Plus tard si vous vous rendez compte que vous avez besoin de passer en paramètre de votre méthode une LinkedList ou n’importe quel autre type de liste, vous devrez en fait changer tous les appels à cette méthode. Et là ça devient l’enfer surtout si votre méthode est appelée dans beaucoup d’endroits. De même pour le type de retour de la méthode, si pour une raison ou une autre vous désirez passer du HashSet au TreeSet vous prendrez cher.

Si maintenant vous aviez la signature de méthode ci-dessous, les problèmes serait résolu en déclarant la signature suivante :

public Set<String> doSomething(final List&ltString> l)

En conclusion

Pour résoudre pas mal de problèmes de maintenabilité dans le code tout en le rendant réutilisable, la règle suivante s’applique : déclarez pour vos objets le type qui contient le sous-ensemble minimal de fonctionnalités dont vous avez besoin. Par exemple si vous n’avez besoin que d’itérer sur le contenu d’un objet il suffit de déclarer celui-ci comme Iterable, ceci permettra de maximiser la réutilisabilité de votre code.

Et pour les langages non-objet comme le C ?

Dans une certaine mesure on peut simuler du polymorphisme en C en prenant des types qui sont relativement compatibles entre eux. Par exemple on peut prendre sockaddr_in et le caster en sockaddr, ce dernier faisant en quelque sorte office de type de base et le premier une spécialisation de ce type.

Et ça permet comme ça de réutiliser la fonction accept en faisant un cast de sockaddr_in en sockaddr.

Et pour les césharpeux ?

Là les amis, ce n’est franchement pas de bol. En effet les interfaces de C# ne sont pas complètes, dans le sens où List implémente des méthodes de IList mais également d’autres interfaces dont IList n’hérite pas. Quand le cas se produit, vous serez hélas obligés de passer par le type List… ou de passer sur une vraie techno. (troll inside 😉 )

Pour aller plus loin

Vous pouvez lire l’excellent, bien que daté, article de Javaworld Why extends is evil, toujours pertinent même après plus de dix ans.

Cet article vous a plu ? Vous aimerez sûrement aussi :

Julien
Moi c’est Julien, ingénieur en informatique avec quelques années d’expérience. Je suis tombé dans la marmite étant petit, mon père avait acheté un Apple – avant même ma naissance (oui ça date !). Et maintenant je me passionne essentiellement pour tout ce qui est du monde Java et du système, les OS open source en particulier.

Au quotidien, je suis devops, bref je fais du dév, je discute avec les opérationnels, et je fais du conseil auprès des clients.

Son Twitter Son LinkedIn

Rejoignez la discussion 4 Commentaires

  • nope dit :

    En fait c’est un peu arbitraire de dire que ArrayList est un mauvais choix non ?

    ArrayList se rapproche d’un tableau et a donc un intéret, celui d’avoir de meilleur perf. Alors oui des perfs c’est pas toujours indispensable.

    Mais exclure tel chose pour en préférer une autre sans comprendre le contexte de l’application en disant arbitrairement : utiliser LIST car si vous voulez passer à LinkedList, ce sera plus simple c’est ne pas réfléchir au départ à ce qu’on a réèllement besoin.

    Il est intéressant de connaitre le besoin de départ en terme de manipulation de données:
    – Ecriture
    – Modification
    – Lecture Seul
    – Suppression
    – Autre ?

    De connaitre sa platform cible:
    – un gros serveur
    – du gros cloud
    – de l’embarquer

    Avec quoi on ba manipuler ces données:
    – un index / iterateur
    – un identifiant type string
    – autre ?

    Ensuite choisir de choisir une/des structures de données en fonction de ce que l’on va avoir comme contrainte:
    – Un tableau pour des manipulation très basique et pour un besoin de perf
    – Un vector pour un peu plus de suplesse
    – Une liste si on a des fort besoin en modification ou si les perf vraiment on s’en fou (ça peut être un vrai argument pour coder plus vite par exemple)
    – Hashmap et autre pour des manipulations un peu plus complexe.

    Au final le titre c’est « bien déclarer ses types de données »
    Alors qu’on a une texte sur « attention ne confondez pas ArrayList avec une list car ce n’est pas une liste »
    (oui c’est intéressant et c’est pas hyper obvious)

    Cela n’a rien à voir.

    Enfin le « elle est importante et à la base de tout langage objet voire non objet « , effectivement c’est important de connaitre la différence entre un tableau et une list. Et c’est vraiment ça qui est important, pas de savoir comment dans un futur hypothétique on pourra refactorer en remplaçant un fonctionnement par un autre.

  • gojul dit :

    Hello,

    En fait ce que je voulais dire par là est que quand on déclare un type quel qu’il soit il faut fournir le moins d’information possible sur le type en question. Pour un objet par exemple on va le déclarer comme étant du type de l’interface contenant le minimum de fonctions requises pour travailler avec cet objet, histoire d’augmenter au maximum la réutilisabilité du code.

    Par exemple si on n’a besoin que de la méthode .iterator() d’un objet, on le déclarera comme Iterable. Et ainsi de suite.

    La même chose s’applique pour des langages du type C où on va prendre les structures de données qui définissent uniquement les champs dont on a besoin pour travailler et pas le reste, toujours pour augmenter la réutilisabilité du code.

    Tout ceci vaut particulièrement pour les API mais c’est aussi une bonne pratique de l’utiliser ailleurs.

    Voir à ce sujet l’article « why extends is evil » indiqué plus haut.

    Après peut-être en effet que le titre de l’article n’est pas le bon, si vous avez mieux je suis preneur ! 🙂

    • Guillaume 31 dit :

      Le troll sur C# est assez nul en fait.

      Il se trouve que l’usage des interfaces existe dans les 2 langages, autant qu’en ADA d’ailleurs.

      IList, en .Net (et pas que en C# mais aussi F# ou VB.Net car tous les langages .Net ont leurs types de bases unifiés par la CLI ) contrairement à ce que vous dites hérite d’autres interfaces (et oui, l’héritage multiple existe en .Net , par le biais des interfaces justement).
      La déclaration ressemble à ceci!
      public interface IList : ICollection, IEnumerable, IEnumerable

      Votre article n’est pas très bien écris.
      Ce qui me semble essentiel, quand on programmez c’est de ce dire « mais qu’est ce que j’attend de cet objet? » (ce que vous dites dans votre commentaire, mais c’est dans l’article qu’on veut le lire 🙂

      Qu’il soit IList ou IEnumerable ? Ce n’est pas la même chose (enlever le I en Java).
      Les 2 interfaces ne font pas du tout le même job.
      IEnumerable peut vous paraitre plus simple et donc moins « bien », mais elle est plus puissante, plus « pure » car plus simple justement.
      C’est meiux de penser en terme d’interface d’objet et pas d’implémentation.
      On ne devait pas se préoccuper de « comment » une classe de Liste fait son job, mais pourquoi.
      C’est l’interface qui nous dit ce que sait faire la classe. Le reste ne nous concerne pas.

      Et plus malin encore ne jamais écrire nous même l’instanciation d’un objet (new c’est le mal car il prend une décision irrévocable quand à l’implémentation de l’objet), plutôt le laisser faire une factory ou un injecteur de dépendance.

      Mais pour cela, il reste de choses à apprendre…

  • gojul dit :

    Bonjour,

    Je ne souhaitais pas lancer de troll sur C#, par contre il est vrai que quand on regarde, en Java le code suivant compile :

    List l = new ArrayList<>();
    l.add(« foo »);
    l.lastIndexOf(« foo »);

    Alors que le code C# suivant ne compile pas :
    IList l = new List();
    l.Add(« foo »);
    l.FindLastIndex(« foo »);

    Le problème est uniquement que IList (et les interfaces dont elle hérite) ne couvre pas toutes les fonction de List, ce qui pose problème pour la réutilisabilité du code dans certains cas car trop d’informations apparaissent dans l’API, à savoir l’implémentation effective de List.

    Après oui je sais que C# implémente aussi l’héritage multiple et pour être honnête :
    – Je trouve que le langage est franchement mieux foutu que Java sur bien des points notamment la gestion des properties
    – Par contre sa librairie standard est à la ramasse par rapport à celle de Java
    – Et la doc MSDN est plus complexe à utiliser que la Javadoc.

Laisser un commentaire