Les interfaces sont-elles un mauvais patron de conception ?
- Notes sur la programmation orientée objets
- Les interfaces sont-elles un mauvais patron de conception ?
- Les bonnes et les mauvaises critiques sur Java — Épisode 1 : Les mauvaises critiques
- Les bonnes et les mauvaises critiques sur Java — Épisode 2 : La syntaxe
- Les bonnes et les mauvaises critiques sur Java — Épisode 3 : Les « bonnes » pratiques
- Java 8 : complexify all the things!
- L'inférence de type en Java : on continue de faire n'importe quoi, yaye !
Bon, je vais pas faire de mystère, j’ai déjà eu l’occasion de dénigrer le principe des interfaces dans de précédents articles donc autant répondre à la question tout de suite : oui, je pense que les interfaces en POO sont une mauvaise pratique. Mais pour pouvoir affirmer une telle chose, il faut pouvoir l’expliquer. Sur ce sujet, mes notes sur la POO constituent une bonne introduction de mon propos, je pense. Alors commençons tout de suite avec le vif du sujet.
L’idée de départ derrière les interfaces
La première formalisation concrète de la programmation orientée objets apparaît avec les travaux d’Alan Kay et son langage de programmation SmallTalk. Une des caractéristiques de la POO est le principe d’objets et d’héritage entre objets. L’héritage est une notion simple qui consiste à dire qu’un objet va en étendre un autre et donc hériter implicitement de ses caractéristiques (variables et méthodes) sans rien avoir à préciser de plus. Le SmallTalk ne propose, à sa création, qu’un héritage simple. C’est le C++ qui va proposer le premier un mécanisme d’héritage multiple. Il est alors possible de créer un objet qui peut en étendre plusieurs autres, parallèlement. Mais l’héritage multiple pose un gros soucis nommé le problème du diamant. Ce problème est simple : si un objet hérite de deux objets différents mais proposant chacun une méthode avec exactement la même signature mais chacun une implémentation différente, quelle implémentation doit prévaloir ? Java va proposer une solution radicale à ce problème en supprimant la possibilité de faire de l’héritage multiple.
Cependant, il y a des cas d’utilisation très concrets dans lesquels il est nécessaire de pouvoir manipuler un objet de différentes manières. Le cas le plus courant se retrouve dans la conception d’interfaces graphiques : tout objet qui représente un élément qui se dessine à l’écran doit pouvoir se manipuler comme un objet qui se dessine à l’écran et donc pouvoir être stocké dans une liste d’objets qui se dessinent à l’écran. Mais d’un autre côté, les objets qui se dessinent à l’écran n’ont pas tous la même nature. Les boutons doivent pouvoir se manipuler comme des éléments cliquables et les labels, pas. Comment permettre alors de faire de l’héritage multiple sans faire d’héritage multiple ? La réponse de Java sera les interfaces.
Les interfaces sont des sortes de classes bridées. Elles ne peuvent être instanciées ni déclarer d’implémentation de méthodes. Tout ce que peut contenir une interface est donc des variables statiques et des signatures de méthode. De fait, l’héritage en diamant est résolu. On permet que l’objet puisse être maniplulé comme un objet d’un certain type, mais ne déclarer aucune implémentation supprime les problèmes d’ambiguités. On s’assure qu’il n’existe qu’une seule hiérarchie d’héritage donc l’implémentation qui prévaut est soit celle de l’objet, soit celle de sont parent, soit celle de son grand-parent, et ainsi de suite, récursivement, jusqu’au parent premier. C’est alors au développeur de fournir une implémentation pour toutes les méthodes déclarées par l’interface.
Là apparaît la première limitation : les interfaces ne peuvent pas être trop grandes. Une interface qui déclare trop de méthodes oblige le développeur à fournir beaucoup d’implémentations pour des méthodes qu’il n’est même pas sûr d’utiliser. Il en résulte que les interfaces sont souvent implémentés partiellement, l’implémentation des méthodes qui n’intéressent pas le développeur consistant à simplement lever une exception de type
NotImplementedException
. Voilà qui est déjà une étrange manière d’aborder le problème, mais la descente dans le chelou ne va pas s’arrêter là.
Les classes abstraites
Il arrive qu’il y ait des cas d’utilisation où il faille forcer le développeur à réimplémenter des méthodes lui-même parce que le cas d’utilisation est beaucoup trop spécifique pour prendre le risque de fournir une implémentation généraliste. Cela arrive, entre autres, dans les bibliothèques d’algorithmes ; par exemple, dans les algos de tri de tableau. Le principe est de permettre au développeur de déclarer un objet comme étant comparable à un autre et permettant de le classer dans un tableau. Il n’est pas possible de fournir une implémentation par défaut d’une telle méthode car, par définition, l’algo de tri n’a aucune idée de la nature des objets qu’il va trier. Il n’a besoin que de savoir si tel objet comparé à tel autre se trouvera avant ou après lui dans l’ordre de classement. Cependant, s’il n’est pas possible de définir d’implémentation par défaut pour cette méthode en particulier. il peut y avoir une nécessité d’en fournir une pour d’autres méthodes de cette classe.
En particulier, j’ai rencontré ce cas d’utilisation lors de l’écriture d’une classe de gestion de liste de lecture pour un projet Android à l’école. Cette classe déclarait un grand nombre de méthodes utilitaires pour effectuer des requêtes en base de données. Ces méthodes n’étaient pas spécialement liées à la gestion de la liste de lecture mais utilisées lors de son instanciation. Le problème est que le grand nombre de ces méthodes utilitaires rendaient le code lourd et verbeux sans avoir de rapport direct avec le travail de la classe elle-même. Comme ce ne sont que des méthodes utilitaires, créer une classe à part entière était inutile puisque ça n’aurait été qu’un objet vide et une consommation inutile de mémoire. Les déclarer statique dans une classe à part entière avec un constructeur privé n’était pas non plus possible puisque ces méthodes agissaient quand-même sur des variables d’instance de l’objet qu’il aurait alors fallu passer en paramètre aux méthodes, ce que je trouve inélégant.
La solution générale à ce problème, c’est la classe abstraite.
Une classe abstraite est simplement une classe qui peut déclarer des méthodes avec des implémentations et des constructeurs, mais qui n’est juste pas instanciable. J’ai donc déclaré une classe abstraire contenant les implémentations des méthodes utilitaires ce qui me permet de les rendre disponibles sans effort et sans avoir à contourner les limitation d’un lien de composition.
Les classes abstraites sont aussi vieilles que le C++ et ont été reprises en Java. Java propose donc trois niveaux de déclaration d’objets : les classes, les classes abstraites et les interfaces qui sont des classes abstraites sans variables d’instance et sans méthodes implémentées.
Java 8 et les méthodes par défaut
La bibliothèque standard de Java repose énormément sur les interfaces. À tel point que c’en est ridicule. À un moment de mon apprentissage de Java, j’ai fini par me rendre compte qu’une grosse partie de mon temps passé à lire la documentation de Java consistait à retrouver une classe qui fournisse une implémentation d’une interface car les 3/4 des méthodes en Java demandent une interface en paramètre. Ouvrir un simple fichier ou produire une conversion de date dans un autre calendrier devient une véritable plaie.
Le plus drôle, c’est que les concepteurs de la bibliothèque standard de Java se sont rendus compte de leur connerie dans la bibliothèque AWT. La programmation évènementielle en Java est une torture pour cette raison très précise. La quantité de code à écrire pour implémenter un simple écouteur d’évènements est longue comme un jour sans pain. AWT fourni des interfaces comme
MouseListener
pour implémenter des réactions à un évènement quelconque mais dans la même bibliothèque, l’API fournit également des classes qui implémentent ces interfaces comme
MouseAdaptater
et qui ne sont que des méthodes vides, qui ne contiennent aucun code ; elles ne font strictement rien.
Pourquoi écrire une interface si c’est pour fournir tout de suite son implémentation ? Autant créer seulement la classe, non ?
Mais tout ça n’est rien en comparaison de la nouveauté que nous réservait Java 8 et que, à mon grand étonnement, tout le monde à considéré comme une grande avancée du langage Java : les méthodes par défaut. Le principe d’une méthode par défaut est de permettre à une interface de fournir une implémentation retenue par défaut si la classe ne déclare pas la sienne. Résumons : une interface est une classe qui ne peut-être instanciée et dont les méthodes ne déclarent pas d’implémentation… sauf si vous choisissez de déclarer une implémentation.
En Java 8, une interface est donc… une classe abstraite. Sans le dire, Java réintroduit donc dans son langage l’héritage multiple et donc, le problème du diamant.
La solution du C++
Le pire dans tout ça, c’est que Java a proposé, par le biais des interfaces, une solution à un problème qui avait déjà été résolu en C++. Dans ce langage, ce problème est appelé une ambiguité. Si le compilateur ne parvient pas à déterminer clairement quelle est l’implémentation qui prévaut, il lève une exception et oblige le développeur à fournir une implémentation explicite de la méthode. Cette solution est très élégante et aurait pu être facilement résolue en Java avec le mot-clef
default
introduit en Java 8. Il aurait alors suffit de déclarer quelque chose du type :
default uneMethode = ClasseMereA.uneMethode;
Même pas besoin de définir une implémentation spécifique, on précise juste que la bonne implémentation est celle de telle classe.
Python aussi permet l’héritage multiple. Cependant, pour déterminer l’implémentation qui prévaut, le langage utilise un algorithme qui permet de déterminer leur ordre de priorité et Scala, un langage compatible avec la machine virtuelle de Java fonctionne sur le même principe. Donc cette limitation est vraiment un choix technique assumé par les concepteurs du langage.
Et l’argument de la robustesse des applications qu’ils avancent ne tient vraiment pas la route car les programmes développés en C++ et en Python ne sont statistiquement pas plus bugués que les autres. Il suffit d’être bien concient de ce qu’on est en train de faire au moment où on le fait. Et, sur une note plus personnelle, j’ai toujours eu du mal à comprendre cet argument qui veut que ce soit le langage qui décide, pour la propre sécurité du développeur, ce qu’il peut écrire ou pas. Se priver de fonctionnalités qui pourraient résoudre des cas d’utilisation un peu exotiques de manière élégante juste parce que le développeur pourrait l’utiliser n’importe comment n’est pas un argument intelligent à mes yeux. Il est ridicule de subventionner des recherches en informatique théorique, de former des ingénieurs pendant des années et leur donner des cours d’algorithmique pour finir par ne les laisser développer qu’avec un langage à roulettes et les empêcher de tomber.
La solution de Groovy et Python
En SmallTalk, en Python, en Ruby et en Groovy, la solution à ce problème est encore plus élégante. Ces quatre langages ont suivis les principes originels de la POO énoncés par Kay en déterminant le type des objets non pas statiquement à la compilation, mais dynamiquement à l’exécution. Cette fixation tardive (late-binding) permet de ne pas s’attarder sur le type des objets eux-mêmes mais sur ce qu’ils sont capables de faire. On appelle ça le typage canard (duck typing). le principe est simple : si ça vole comme un canard, que ça gueule comme un canard et que ça nage comme un canard, alors c’est un canard.
Avec une telle manière de programmer, on en a rien à foutre des interfaces. Savoir si tel objet respecte tel contrat définit par telle interface et peut être traité comme tel n’a strictement aucun intérêt. La seule question qui a du sens est : « est-ce que mon objet possède la méthode que j’essaie d’appeler ? ». Si oui, alors le langage considère que le développeur a fait son boulot et là, c’est à lui de prendre ses responsabilités. Si non, il lève une exception pour dire au développeur qu’il a merdé quelque part.
C’est une solution simple et je n’ai d’ailleurs pas connaissance que les interfaces existent en tant que tel en Python ou en Ruby.
Il faut se rendre à l’évidence : Java est un mauvais langage, fondé de mauvais principes et avec de mauvaises idées qui ont mal tourné. Il existe, certes, des alternatives. J’en ai cité quelques-unes dans cet article mais, malheureusement, Java continue d’occuper la haut de la chaîne et d’être le langage dominant en entreprise, parce que personne, en école d’ingénieur, ne forme à d’autres langages…
Déjà 21 avis pertinents dans Les interfaces sont-elles un mauvais patron de conception ?
Les commentaires sont fermés.
Ton article me fait pense à celui d’oncle Bob : http://blog.cleancoder.com/uncle-bob/2015/01/08/InterfaceConsideredHarmful.html
De manière générale je ne suis pas très fan de l’héritage, auquel je préfère la composition. J’aime d’ailleurs assez
go-lang pour son principe d’interfaces implémentées implicitement, qui donne un air de python à l’utilisation.
Après le souci de la composition, c’est qu’on peut avoir à faire pas mal de transfert d’appel.. mais je vis avec ^^
Merci pour l’article. C’est très drôle
Pourquoi ne pas vous mettre au C plus plus en autodidacte? Vous allez apprécier à mon avis (surcharge d’opérateurs, passage par adresses ou par références, PAS d’interfaces, PAS de garbage collector) Je suis étudiant en première année de BTS Systèmes Numériques, j’ai fait du java l’année dernière quand j’étais en DUT informatique (que j’ai raté). On va dire que je connais les bases de java et de la POO(notions de classes, d’objets, d’héritages, je commence à comprendre le polymorphisme…, liaison dynamique… que dalle). Je sais ce que c’est qu’une interface , je sais que ça sert à définir un type abstrait, mais j’admets que moi aussi, j’ai du mal à en comprendre l’utilité. Quand est ce qu’on s’en sert ? Quand préférer les classes abstraites?
Pourquoi la plupart des gens disent que Java, c’est LE langage orienté l’objet? (Ca n’a pas l’air faux, même pour une simple saisie au clavier, il faut instancier la classe Scanner, les String sont des Objets, tous les types primitifs ont leur wrapper dans le SDK et il y a même ce mécanisme d’autoboxing qui permet de convertir un type primitif en un objet).
Et puis moi ce ne me plait pas en java, c’est que le contact avec la machine, il est vraiment superficiel contrairement au C ou au C ++. On est obligé de passer par des méthodes natives qui font appel à du code écrit en C, qui LUI fera ce pourquoi il est appelé. (langage de haut niveau obligue).
Et puis les interfaces graphiques en Java, MON DIEU QUELLE HORREUR. Je n’y ai JAMAIS RIEN compris. Mais est ce que ce n’est lié que à ce que vous expliquez dans l’article?
Oui, je déteste Java. Je déteste ce qu’il m’oblige à faire (à savoir des interfaces, l’héritage simple, beaucoup trop de liens de composition), je déteste qu’il y ai si peut d »opérateurs et qu’ils ne soient pas surchargeables, je déteste que la manipulation des chaînes de caractères soient si ridiculement compliqué, je déteste qu’on puisse pas déclarer des listes ou des dictionnaires en compréhension, je déteste avoi à instancier des Builder de Maker de Factory pour pouvoir lire un con de fichier, je déteste qu’il n’y ai pas de fonctions anonymes (nouvelles en Java 8)… Je déteste Java mais c’est tout ce qu’il y a en entreprise. Alors je fais avec. Heureusement, les entreprises utilisent beaucoup de biblios qui rendent les choses plus supportables. Il y a même des fois où je trouve moyen de m’éclater à faire du Java. Mais je préfèrerais faire du Groovy.
Je connais déjà C++ et j’aime pas plus sauf en duo avec la biblio Qt. Le reproche que j’ai à faire est plus proche du reproche que je fais au C : la bibliothèque standard est ridiculement petite. C’est un peu comme partir à la chasse avec sa bite et son couteau. Qt rend le tout franchement bien avec des objets pour presque tous les cas d’utilisation possibles. Et surtout, il offre le systèmes de signaux/slots qui me rend tout chose !
Je saurais pas te répondre (on peut se tutoyer, hein ;)). Ou plutôt, je saurais pas te donner la réponse académique qu’attendraient tes profs. Ma réponse à moi, c’est : jamais. Une interface, tu peux mettre que des signatures de méthodes et des variables statiques, donc ça sert à que dalle. Pour moi, les interfaces, c’est juste une rustine sur le langage parce que les développeurs étaient trop fénéants ou trop cons pour implémenter un héritage multiple correct. Le seul cas d’utilisation, c’est quand t’as pas le choix, c’est à dire quand l’API t’oblige à implémenter une interface. À l’extrême limite, quand t’as besoin de simuler un héritage multiple (mais c’est assez rare). Pour le reste, il n’y a strictement rien qu’une interface permet qu’une classe abstraite ou un lien de composition ne permette pas.
Pour les classes abstraites, ça s’utilise pas souvent mais tu t’en serts en général quand tu veux mutualiser un bout de code commun qui est partagé entre plusieurs objets mais que ce bout de code ne se suffit pas à lui-même et qu’il ne permet pas de définir un objet à part entière. En général, tu mutualises du code utilitaire dans une classe abstraite pour alléger le code dans les autres objets. Ça te permet de définir des prototypes d’objets incomplets. Comme par exemple un véhicule ne se suffit pas à lui-même mais possèdes des propriétés qui sont partagées entre voiture et scooter.
Parce qu’ils disent de la merde ? C’est une histoire assez peu connue, en fait, mais comme je l’explique dans l’autre article (c’est un peu touffu, conceptuellement, j’ai moi-même eu beaucoup de mal à comprendre la différence), la plupart langages objets modernes se sont concentrés sur l’aspect objet du concept de départ alors que l’idée centrale, c’était celle des messages qui transitent en les objets. Pour Java, ces passages de messages se résument à des appels de méthodes mais c’est pas la seule solution.
Oui mais c’est ça le problème : un vrai langage objet ne connaît pas de type primitifs. Le fait que Java ai dû rajouter des wrapper et de l’autoboxing (qui n’est arrivé quavec Java 5, hein !) prouve qu’il y avait déjà une couille au départ…
Bof… Ça je m’en tape un peu. Les gens qui préfèrent faire du C pour être des vrais, parce que eux, au moins, ils manipulent la mémoire, me donne vraiment l’impression de faire de la masturbation mentale. La machine virtuelle de Java a pu poser des problèmes de performance à une époque. Plus aujourd’hui. Les ordi sont suffisamment puissants pour faire tounrner du bon gros bytecode Java bien poisseux sans broncher. Minecraft est entèrement écrit en Java, c’est dire…
Oui, c’est lié. Le développement d’interfaces graphiques repose énormément sur la programmation évènementielle. Sauf que Java, il sait pas faire nativement. Dès que tu veux réagir à un tout petit évènement à la con, c’est tout de suite obligé de sortir ton bon gros pattern observer qui débarque avec sa hâche et sa chemise à carreau. La gestion de l’évènementiel en Java est à chier. Qt ou C# sont bien plus agréables de ce point de vue. Tout passe part des mécanismes d’évènements/callback (un pattern observer caché et natif, en vrai), c’est très agréable.
Je suis d’accord avec toi sur ce point là, c’est regrettable. (c’est l’effet c++)
« je déteste que la manipulation des chaînes de caractères soient si ridiculement compliqué »
C’est à dire? Ils l’ont trop simplifié à votre gout? Ou alors, à trop vouloir la simplifier, ils l’ont compliqué ?
« qu’on puisse pas déclarer des listes ou des dictionnaires en compréhension »
J’ai pas utilisé les collections proposées par le SDK,la seul e que je connaisse, c’est la liste contigue, que j’ai codé et il y a aussi la liste chainée, mais ça c’est le prof qui nous l’a codé, en démo. Et les dictionnaires en compréhension, je ne connais pas.
» je déteste avoi à instancier des Builder de Maker de Factory pour pouvoir lire un con de fichier »
En instanciant la classe Scanner, on peut le faire. Par contre, je ne me souviens plus quel constructeur le fait dans cette classe.
« je déteste qu’il n’y ai pas de fonctions anonymes » :
Fonctions anonymes, je connais mais j’irais voir sur google.
« Il y a même des fois où je trouve moyen de m’éclater à faire du Java. »
Tant mieux pour toi
« Je connais déjà C++ et j’aime pas plus sauf en duo avec la biblio Qt »
Je commence à connaitre un petit peu le c++. Qt je connais pas mais je sais que ça sert à faire des interfaces graphiques avec des widgets… et pleins d’autres bonnes choses de ce style.
« la bibliothèque standard est ridiculement petite. »
Moi aussi je reprochais la même chose au C, mais c’est normal, quand à côté, on apprend un langage où le travail est maché (parfois de manière exagéré).
» Le seul cas d’utilisation, c’est quand t’as pas le choix, c’est à dire quand l’API t’oblige à implémenter une interface. À l’extrême limite, quand t’as besoin de simuler un héritage multiple (mais c’est assez rare). Pour le reste, il n’y a strictement rien qu’une interface permet qu’une classe abstraite ou un lien de composition ne permette pas. »
Bah à la base, il me semble que c’était surtout pour « remédier » à l’absence de l’héritage multiple.
« Oui mais c’est ça le problème : un vrai langage objet ne connaît pas de type primitifs. »
Oui mais dans ce cas, si dans ton main, tu dois créer des variables de types primitifs (mettons des entiers), que tu dois les utiliser pour l’appel d’un constructeur, comment tu fais sans type primitifs ? Et puis quand tu code une classe objet (avec les attributs et les méthodes), pour pouvoir décrire l’objet, créer un nouveau type, il faut bien partir de quelque chose, non? Et en programmation procédurale, on fait comment? (Tu te dis développeur, donc ma question va surement te paraitre idiote, mais c’est là qu’on voit que je débute et comme je l’ai dit, jsuis loin d’être un as de la prog, je n’ais qu’un ans de DUT).
Nan, c’est juste que, 20 ans après Python, y’a toujours pas de putin de chaînes template ! C’est pas compliqué, pourtant ! En Groovy, on fait ça :
def name = "Patrice"
print "Coucou, ${name}" // Affichera "Coucou Patrice"
C’est ridicule qu’il n’ya ai toujours pas ça en Java ! Pareil pour les chaînes multilignes ! En Groovy tu fais :
"""Une chaîne
sur plusieurs
lignes"""
En Python, ça existe depuis des années. Pareil en Ruby. Mais en Java, rien…
Bah en gros, en Groovy, tu fais ça:
def liste = ["Item 1", "item 2", "Item 3"]
Et t’as une liste. En java, pour faire tout pareil, tu fais:
ArrayList list = new ArrayList();
list.add("Item 1");
list.add("Item 2");
list.add("Item 3");
C’est ridicule. Pareil pour les dictionnaires (les Map et tou les autres associés comme LinkedHashMap). En Groovy tu fais:
def liste = [item1: "Hey !", item2: "Yo !", Item 3: "Salut !"]
Et t’as une LinkedHashMap… Je te conseille de lire le début de mon article sur Groovy et Android pour comprendre un peu mieux ce qui merde pour moi en Java.
Nan, c’est pas une classe spécifique. C’est une façon de parler quand je dis ça. Je fais référence au fait que dès que tu veux faire un truc un peu compliqué en Jav (comme du réseau, p.e), tu dois instancier des cascades d’objets à la con parce que les développeurs sont pas foutus de coder correctement. Ça s’appelle le pattern factory ou fabrique en français. Et y’en a partout dans la biblio de Java. Ils font ça parce qu’ils sont pas foutu d’implémenter des constructeurs avec des paramètres optionnels, comme en Groovy. Du coup, ils font ça pour éviter d’avoir à se taper l’écriture de 18 constructeurs différents dès que l’objet devient un peu complexe. Cet article te montre comment faire ça en Groovy (en anglais).
Bah, en gros, c’est la possibilité de manipuler une fonction comme un objet et de les déclarer sans signature. Encore un élément qui fait que Java n’est pas un langage objet : les méthodes ne sont pas des objets. On appelle ça généralement un foncteur. Le principe est un peut sembler brainfuck mais en fait il est tout con : il s’agit juste d’un objet qui possède l’opérateur
()
. En Java, comme d’hab, faut que tu crée un objet à la con qui implémenta l’interfaceCallable
. C’est pas natif. En Groovy, c’est simlé par l’objet Closure qui a une méthodecall()
dont l’opérateur()
est un raccourci. Tu peux les créer juste en déclarant un corps avec des accollades :def closure = { print "Ceci est un Closure" }
closure.call() // Affichera "Ceci est un Closure" à l'écran
closure() // Pareil qu'au dessus
Et l’opérateur
.&
te permet de générer un Closure à partir d’une méthode d’un objet :def uneChaine = "Coucou, ceci est une chaîne"
def closure = chaine.&toLowerCase
print closure() // Affichera "coucou, ceci est une chaîne"
C’est con comme la pluie, mais ça évite d’avoir à fournir des interfaces à la con à chaque fois que tu veux faire des callback. T’as plus besoin de faire passer ton objet pour autre chose avec une interface et aller le ballader ailleurs, t’as juste besoin de passer un Closure là où ça va bien…
Principalement, ouais. Mais on peut l’utiliser pour plein d’autre choses. La bibliothèque est vraiment énorme. T’as toutes les structures de données dont tu as classiquement besoin en algo : piles, files, listes, arbres, t’as des objets pour gérer le réseau, les threads, la concurrence, le temps, etc. Elle est vraiment cool !
C’est pas une question de mâcher le travail, vraiment. Passer son temps à réinventer la roue est vraiment une très mauvaise pratique. Je comprends que ça puisse faire plaisir de pouvoir se dire qu’on l’a fait de ses mains, mais ça n’a vraiment aucun intérêt au-delà de ça. Franchement, quand tu manipules des chaînes de caractères, t’as pas envie de recoder ta méthode pour la splitter, la tokenizer ou faire des recherches d’expression rationnelles, c’est ridicule. Pareil pour les structures de données : les biblios sont étudiées pour ça.
Tu ne pourras jamais créer une pile ou une file toi-même avec une meilleure gestion de la complexité que Qt. Quand un outil existe, on l’utilise. S’il est pas bien, on l’améliore. Mais passer son temps à réinventer ses outils, ça c’est parfaitement stupide.
Y’a vraiment très peu de projets qui ont des besoins spécifiques aujourd’hui. Et surtout, très peu de projets où le besoin de gérer soi-même la mémoire est vital. Ça se restreint vraiment à tout ce qui tourne autour de l’embarqué pour des questions de performances et au développement de système d’exploitation. Pour tout le reste, dire qu’on est un vrai parce qu’on gère sa mémoire comme un grand, c’est de la masturbation intellectuelle. Le besoin n’existe jamais vraiment et tu prends au contraire le risque de faire plus de la merde qu’autre chose. D’autant que, bon, les gens qui disent qu’en C++ on est des vrais parce qu’on gère sa mémoire connaissent vraiement pas grand chose au C++. Il suffit d’initialiser toutes ses variables sur la pile plutôt que sur le tas pour ne plus avoir à détruire aucun objet et bénéficier d’un garbage collector gratos… -_-
C’est bien ce que je dis : ça ne sert à rien. C’est là juste parce que les devs de Java n’ont pas été foutus de faire un héritage multiple correct… Encore un fois : perso, je fait de l’héritage quand j’ai besoin, des liens de composition quand j’ai pas le choix, mais j’ai jamais eu besoin de créer une interface à la con…
Ce sont des objet. En Python, par exemple, ce sont des objet. Tout est un objet en Python. Si tu tapes
myInt = 1
, c’est un objet, et, en fait, ce que tu as tapé, c’est :myInt = int(1)
. Tout est toujours un objet.Des objets ! Toujours des objets ! Tout est un objet. C’est ce qu’il faut bien comprendre en fait en vraie POO : l’objet, c’est une abstraction. Toi, tu ne comprends pas ça parce que tu résonnes encore en terme de comment tout ça fonctionne dans la mémoire. Bien sûr, dans la mémoire, t’as des types primitifs. Mais ça, c’est une fois que tu as traduit ton code en assembleur. Mais quand tu développes en Python, tu ne cherches pas à savoir comment ton objet est vraiment représenté dans la mémoire. C’est un objet, point. Si tu ne devais retenir qu’une chose à propos de la POO, c’est ça : un objet est une entité à laquelle tu peux passer des messages. Le passage de message le plus con du monde, c’est l’appel de méthode. Et, en vraie POO, tout est objet. Donc tu peux passer des messages à tout. La question de comment tout ça est représenté en mémoire n’a aucune importance. La seule chose à savoir, c’est que tout ce que tu manipules peut recevoir un message : les entiers, les chaînes, les tableaux, les fonctions, tout !
Alors attention, là, ça devient un peu tricky : la POO, ça peut-être de la programmation procédurale ou pas. C’est généralement de la programmation procédurale, d’ailleurs. La programation procédurale repose sur le principe de mutabilité des variables. C’est-à-dire qu’un fois que tu as déclaré une variable, tu peux la réassigner. Ça, c’est possible en POO, rien ne l’empêche. La plupart des langages de POO reposent là-dessus, d’ailleurs parce qu’il sont généralement des héritiers du C.
Je pense que ce que dont tu veux parler, c’est de la différence entre le C et Python, par exemple. C’est assez simple : en C, il n’y a pas d’objet, il n’y a que des structures de données. C’est à dire que tu as des types primitifs et des types complexes (les
struc
) d’un côté, et les fonctions de l’autre. Les deux sont séparés, point. Par contre, en objet, tu n’a pas forcément de séparation entre les deux. Un objet, c’est des données + des méthodes. Bon, en vrai c’est plus subtil que ça, mais l’idée de base est là.J’espère que j’ai réussi à t’éclairer un peu. C’est pas facile d’expliquer tout ça pour moi parce que, déjà je connais pas ton niveau en dev donc je sais pas trop quelq concepts ont à ta porté et lesquels je dois plus développer, mais surtout parce que c’est des connaissances que j’ai moi-mêmes acquises avec de nombreuses lectures et en essayant plusieurs langages.
« Si tu tapes myInt = 1, c’est un objet, et, en fait, ce que tu as tapé, c’est : myInt = int(1). Tout est toujours un objet. »
Ca voudrait en fait que si je suis ma logique qui est très scolaire pour l’instant, il y a déjà un constructeur pour même pour une donnée de base comme un entier?
« C’est pas facile d’expliquer tout ça pour moi parce que, déjà je connais pas ton niveau en dev donc je sais pas trop quelq concepts ont à ta porté et lesquels je dois plus développer, mais surtout parce que c’est des connaissances que j’ai moi-mêmes acquises avec de nombreuses lectures et en essayant plusieurs langages. »
Oui je comprend, mais là en fait si je te suis, il faudrait remettre en cause tout une partie de mes connaissances en programmation.
Rien que l’histoire du type primitif qui ne devrait pas exister (sinon pourquoi l’autoboxing en Java?) qui est un objet et qui n’existe qu’une fois le code traduit en assembleur, déjà c’est « énorme ».
Pareil pour les fonctions, cad que pour moi une fonction ou une méthode, c’est un bout de code avec un identificateur et des paramètres éventuels, qui fait un traitement et qui renvoie éventuellement des résultats, ça rend un programme plus facile à maintenir car quand il y a une erreur, on ne la corrige qu’une seule fois… (je te refais pas le refrein, tu le connais déjà). J’aurais jamais imaginé ça comme un objet surtout avec ma vision purement scolaire de la POO(entièrement fausse?). Car comment je décris un bout de code dans une classe, qui est la seule référence que je connais en objet, sachant qu’il y a d’autres choses que les classes? Comment je respecte le principe d’encapsulation? Comment je fais pour définir ma fonction comme l’instance d’une classe?Tu vois ce que je veux dire? Et peut être qu’ en Python, on n’utilise pas les classes, je n’en sais rien, je ne connais pas Python dans les détails. Et tu vois, ce qui pour moi est le pire, c’est que j’ai appris les bases de la programmation avec Java, mais le refrein sera le même pour tous les autres langages de la famille C(c++ , C# …). Et c’est la même pour les type primitifs. Ma vision se limite à celle qu’on nous apprend à l’IUT ou en BTS, c’est à dire, le type primitif et les autres et effectivement cette limitation de la réflexion au niveau du fonctionnement en mémoire.
Oui, il y a un constructeur pour tout
Bien sûr, dans le cas des entiers, ce constructeur est un peu caché. Parce que t’y fait appel dès que tu fais
var = 3
. Mais pour t’en convaincre, tu peux ouvrir une console Python, taper(3).__str__()
pour voir que tu peux appeler des méthodes même sur des entiersOui, effectivement. Mais je ne te demanderai pas de le faire, car ce n’est certainement pas ce que tes profs attendent de toi. Tes profs attendront très certainement plus de toi que tu dises que Java est un langage objet, parce que c’est aussi ça qu’on leur a appris à eux et qu’ils ne se sont en fait jamais posé la question. Moi-même, je ne cherche pas à démontrer que mes profs qui m’affirment ça ont tort. J’écris d’ailleurs sous pseudo pour éviter que s’ils tombent un jour sur l’article, ils fassent le lien avec moi.
Pas entièrement fausse, non, mais incomplète. Essie un jour de jouer avec la méthode
operator() ()
du C++, ça te donnera des résultats intéresssants. On appelle ça des foncteurs et c’est assez pratique quand on sait les utiliser. Ça transforme un objet en fonction, c’est très rigolo. Et surtout, ça t’apprend que, finalement, une méthode, ça n’est rien de plus que l’opérateur()
appliqué à un truc (appelons ça une variable ou un objet). C’est-à-dire… Un passage de messageNan, pas du tout ! ^__^
Mais je pense que je ne te suis pas essentiellement parce que tu es toi-même perdu. Cette approche de la progrmmation retourne carrément le cerveau et il faut avoir fait un peu de théorie des langages et de programmation fonctionnelle pour l’appréhender un peu mieux :p
Je ne pense pas pouvoir t’expliquer ça mieux par l’intermédiaire de commentaires sur un article. Il faudrait que tu fasses toi-même joujou avec des foncteurs et des langages fonctionnels pour mieux te rendre compte. Mais effectivement, la vision de la POO qu’on t’apprends à l’école est carrément tronquée
Si si, Python utilise des classes. Mais c’est là où c’est un peu tricky, c’est que Python, comme Groovy, permettent d’écrire du code en-dehors de toute classe contrairement à Java ou t’est obligé de créer une classe pour faire la moindre petite addition. Pourtant, ces deux langages restent des langages de POO bien plus respectueux de la formalisation d’Alan Kay que Java…
En IUT et en BTS, ils ne t’en apprennent pas plus parce qu’en vrai, tu n’a pas besoin d’en savoir plus. T’as pas besoin de te prendre le choux avec ça comme si tu découvrait qu’on t’avait menti toute ta ve, hein
Savoir ça ne changera pas fondamentalement ton métier. Ça ne fera pas intrinsèquement de toi un bon développeur ni même un meilleur développeur. Ce que j’expose ici touche bien plus à l’informatique théorique qu’autre chose. Savoir ça n’a, en vrai, strictement aucune application en entreprise et ça n’a pas spécialement changé ma manière de développer (je n’utilisais déjà pas les interfaces, de toutes façons ; mais au moins, maintenant, je sais expliquer pourquoi :p).
Ça aide juste à se rendre compte qu’il n’y a pas qu’une manière de développer et surtout, contrairement à ce qu’en disent les fervants défenseurs de Java, pas qu’une seule bonne manière de développer. Un bon nombre de patterns classiques de Java, par exemple, bien qu’ils apparaissent comme une bonne pratique dans le monde Java, m’apparaissent à moi comme du code de porc ou des contournements sales pour palier les carances du langages. Mais quand je dois les utiliser, bah je les utilise. Parce que, quoiqu’il arrive, je développe pas tout seul comme un autiste. Je fais partie d’une équipe.
C’est peut-être la chose la plus importante à savoir : il n’y a pas, en développement, une vérité, une bonne manière de programmer. Il n’y a que des usages. À toi de choisir lesquels te conviennent du moment que tu sais quelles en sont les conséquences et pourquoi tu as fait ce choix. Et le jour où quelqu’un vient t’indiquer une autre manière de faire, si elle te paraît sensée, adopte-là. Sinon, rejette-là. Mais ne laisse jamais quiconque t’imposer une bonne manière de programmer. Si la personne en face n’est pas capable de t’expliquer pourquoi c’est la bonne manière, alors il y a de fortes chances que c’en soit une mauvaise en réalité
Si je te pose la question c’est que j’ai regardé un peu les tutos sur ce langage et j’ai été assez stupéfait des ressemblances syntaxiques et de conception des deux langages. C’est vrai, regardes, en C# tu n’as pas d’héritage multiple, pour écrire le moindre petit algo, tu dois créer une classe. Je crois aussi (mais à vérifier) qu’il existe une super classe NSObject (Dailleur crois tu que l’idée de la super classe, du genre Object soit une connerie? ). C# reprend le concept d’interface qui selon toi est inutile mais avec des différences que je ne connais pas. Il me semble aussi que l’on peut surcharger les opérateurs en C# mais c’est encore à vérifier.
Pense tu que les concepteurs du C# ont fait la même connerie que ceux de Java?
J’en parlais à propos des signaux/slots de Qt, en disant qu’il n’y avait qu’en C# que j’avais retrouvé un mécanisme de gestion des évènements aussi performant, avec les event handlers.
Je n’ai pas utilisé le C# autant que le Java donc je le connais pas aussi bien, je te l’avoue. Mais, globalement, C# ajoute beaucoup de fonctionnalités très pratiques à Java : la surcharge d’opérateur, les getters et setters natifs, l’évènementiel natif, les expressions lambdas (nouvelles en Java 8), les chaînes templates et multilignes, etc.
Mais, si effectivement, C# ne propose pas d’héritage mutiple et repose aussi sur les interfaces, je dois bien t’avouer que c’est quelque chose que je viens de découvrir. Je ne sais pas comment se sont débrouillés les concepteurs du langage mais je ne souviens pas avoir jamais eu à implémenter une quelconque interface.
Quant aux ressemblances syntaxiques, elles ont une raison historique : Microsoft avait, à la base, proposé a propre implémentation de Java : le J++. Lorsque Sun a attaqué Microsoft en justice pour lui interdire d’utiliser ses techno, MS a créé le C# et le J#. Bon, y’a aussi le fait que C# est autant basé sur Java que sur C++ et que Java, lui-même, se voulait être un langage qui remplacerait le C++. Donc c’est pas étonnant qu’on retrouve de forte similitudes syntaxiques. Tu retrouveras les mêmes avec le D :p
Oui, mais faut pas confondre le C# et le Java avec le Python ou le Groovy qui sont des langages de script. Ce sont vraiment deux utilisations différentes. Si je veux écrire un bout de code pour trier mes photos, c’est sûr que je vais pas avoir envie de créer une classe systématiquement.
Le problème, c’est que devoir créer une classe systématiquement, ça allourdi profondémment le code d’un manière qui n’est pas nécessaire quand tu apprends la programmation. C’est pour ça que c’est dommage, selon moi, de faire commencer les étudiants sur Java ou C# direct plutôt que de leur faire faire des petits algos de tri en Python ou en Groovy d’abord.
Bon, tu l’auras compris, le top du top reste pour moi Groovy qui permet à la fois de faire du script et de la grosse programmation de logiciels critiques. Python aussi permet de faire de la grosse programmation de logiciels critiques. Il y a des biblio de calcul formel qui sont carrément écrits en Python. Mais je sais pas pourquoi, ça m’a l’air moins répendu que pour les autres langages. Les habitudes de la profession, sûrement.
Non, pas du tout. C’est même une bonne pratique. Ça permet de s’assurer que tous les objets qui seront utilisés présenteront un panel de méthodes communes. Les méthodes
hashCode()
etequals(Object obj)
en Java sont, par exemple, très utiles dans la gestion des listes. Elles permettent respectivement d’identifier un objet de manière unique et de le comparer à un autre pour déterminer si c’est le même. Elles permettent de s’assurer qu’on n’insère pas deux fois le même objet dans les structures de données qui n’acceptent pas les doublons et de supprimer le bon objet d’une liste en ne passant que sa référence. La méthodetoString()
est aussi très utilisée. Elle permet de donner une représentation lisible d’un objet et, surchargée, elle est très utile pour le debug. C’est elle qui est utilisée quand tu passes un objet quelconque àSystem.out.println
.Nan, le gros problème, pour moi, c’est que cette super-classe soit utilisée comme prétexte pour ne pas avoir d’héritage multiple. Car comme toute classe en hérite implicitement, on crérait forcément un problème du diamant. Personne ne s’est dit à aucun moment qu’on pourrait tout simplement en faire un cas particulier.
D’autant que c’est vraiment une fausse excuse, vu que c’est déjà un cas particulier : c’est la seule classe qui peut s’hériter implicitement. Donc c’était vraiment de la mauvaise volonté que de ne pas le faire. D’ailleurs, sur internet, tu verras beaucoup de gens t’expliquer que l’héritage multiple, c’est mal sans réussir à justifier correctement leur propos. C’est bien la preuve d’un certain dogmatisme et pas d’une règle de bonne pratique.
Oui, on peut. Ça je peux de l’assurer, je l’ai fait. C# reprend d’ailleurs sur ce point la même syntaxe que C++ avec le mot-clef
operator
qui permet de surcharger un opérateur. La syntaxe classique est :public static T operator +(T t1, T t2){ /* ... */ }
Si tu parles de l’héritage simple et des interfaces, oui. Il n’est pas question de langage ici, vraiment. C’est juste une question de design. Oui, je pense vraiment que l’interface et l’héritage simple sont une très mauvaise pratique qui complexifient le langage de manière inutile. C# a enlevé un peu de complexité puis que les mot-clefs
extends
etimplements
on disparu au profit d’un opérateur d’héritage unique pris au C++ : les deux-points. Ça évite de se demander si on hérite d’un interface ou d’une classe ce qui est généralement une bonne pratique puisque c’est précisémment ce que le polymorphisme est censé cacher.Mais au final, l’héritage simple empêche toujours la possibilité de considérer un même objet selon diférentes natures. Et l’interface n’est vraiment à mes yeux qu’une rustine dégueulasse.
Bon, en tout cas merci hein, c’est toujours bien d’avoir l’avis d’un vrai développeur, même si je trouve que ça le fait pas trop de mettre en avant les faiblesse de Java puis de réutiliser son code ailleurs (je respecte quand même votre opinion). Celà dit même si j’aime bien coder en Java, je ne le trouve pas parfais, loin de là.
Les interfaces permettent de décrire le comportement d’un objet en se fichant de son implémentation. Cela ouvre la porte au polymorphisme, à l’injection de dépendances, etc. Imaginons par exemple une interface UserReposiory qui définit une méthode findOneByEmail(). on pourrait ainsi avoir une implémentation MysqlUserRepository, InMemoryUserRepository, etc. Ainsi, par exemple lorsque tu fais tes tests tu peux directement mocker l’interface et quand tu auras besoin de récupérer un User tu demanderas un objet du type UserRepository tout simplement.
En en discutant avec un collègue, il me disait que ce qu’il trouvait bien à l’interface, c’est qu’elle te garantissait qu’aucun effet de bord serait généré par l’implémentation. D’accord, je peux l’entendre. Sauf que ça pose au moins un gros problème pour moi : ça pète l’encapsulation puisque tu es obligé de te demander si tu hérites d’une interface ou non. Et c’est à mes yeux ce qui fait la lourdeur du JDK. Le dogmatisme des devs de Java fait que tout passe par des interfaces. Alors ok, l’abstraction, c’est bien mais quand vient le moment où tu dois retourner sur du concret, tu passes des heures à trouver la bonne implémentation de l’interface et ça, c’est chiant.
Et la composition aussi me pose un très gros problème puisque c’est un pattern qui pète une des règles fondamentales du développement de logiciels : DON’T FUCKING REPEAT YOURSELF. Sauf que quand tu composes, tu dois écrire toutes les méthodes déléguées. C’est du temps perdu. Et c’est pour ça que l’héritage n’a pas disparu — même en Java ! — et qu’on continue à l’enseigner en école d’ingé. Et c’est pour la même raison que Java 8 a introduit les méthodes par défaut dans les interface, réintroduisant par là-même, l’héritage multiple. Renseigne-toi, si ce n’est déjà fait, sur les raisons qui ont poussé les développeurs de Java à réintroduire des implémentations dans Java 8 : ce sont les lambdas. Le problème que posaient les lambdas, c’est que toutes les classes qui implémentaient
Collection
, par exemple, devaient toutes se voir rajouter des implémentations de la méthodeforEach
introduite avec les lambdas. Et ça, ça faisait chier les devs de Java. La raison pour laquelle ils ont réintroduit l’héritage multiple est en fait exactement la même que celle pour laquelle ils ne l’avaient pas introduit dès Java 1 : la fénéantise.Chaque pattern a ses avantages et ses inconvéniants. Le choix devrait, en dernier ressort, être toujours laissé au développeur. Ça ne devrait pas être au langage de décider ce que le développeur peut ou ne peut pas faire.
C’est essentiellement ça que je reproche à Java et ce que cristalise l’interface : un dogmatisme fini et bête.
PS : le type de la vidéo que tu cites commence par un principe malheureusement assez bête : les interfaces décrivent ce qu’un objet peut faire quand l’héritage détermine ce qu’il est. Sauf que c’est la même chose. Et c’est exactement la raison pour laquelle Java n’est pas un langage orienté objet. Le principe de base de l’orienté objet tel que l’a décrit Alan Kay avec SmallTalk, c’est que les objet ne sont que des entités qui communiquent par passage de messages. La problème, c’est que tout le monde s’est focalisé sur le principe de l’objet alors que le concept important était celui du passage de message. C’est la raison pour laquelle tout langage vraiment orienté objet se fiche toujours de la nature des objets auquel il passe des messages tant qu’il peut lui passer des messages. L’orienté objet présuppose donc fondamentalement le typage dynamique. Si ce que j’ai à en dire t’intéresse, je le détaille plus amplement ici.
PS 2 :Tu remarqueras aussi que le type de la vidéo donne ses exemples en JavaScript, langage qui ne propose absolument pas le concept d’interfaces ni même, jusqu’à la spécification ES6, les classes et l’héritage.
Tu ne peux étendre que d’une seule classe abstraite mais tu peux implémenter plusieurs interfaces. Avec la classe abstraite tu hérites d’une classe de base que tu étends, avec une interface tu acquiers une capacité à faire certaines choses. Certains suffixent leurs noms d’interface par « able »: être apte à.
C’est aussi pour cela que généralement les classes abstraites deviennent des fourre tout immondes alors que les interfaces te permettent d’avoir un code réutilisable, modulaire et testable.
Oui, c’est aussi le principe du typage fort, on type les arguments des méthodes et si un mauvais type est passé, ça pète. Quand tu types avec une interface, tu passes un contrat avec ta dépendance, ça permet une meilleure lisibilité du code, une meilleure testabilité, une meilleure cohérence. Mais comme toujours, cela demande plus de rigueur et de lignes de codes. Ca a l’air de servir à rien pour un petit projet qui peut se faire dans un fichier en 30 lignes de script mais ça sert beaucoup quand tu bosses sur un gros projet et que tu deal avec de la complexité et beaucoup de logique métier. Y’a qu’à voir les principes SOLID https://en.wikipedia.org/wiki/SOLID_(object-oriented_design) que tout bon développeur expérimenté se doit d’essayer de respecter, pour se rendre compte que les interfaces sont très utiles (voir le Interface segregation principle).
Je t’avoue que je ne comprends pas ce que tu veux dire car pour moi l’encapsulation d’un objet est sa capacité à ne fournir qu’une seule manière de modifier son état (à travers des méthodes), l’implementation d’interface rend la chose plus claire car en regardant les interfaces qu’il implémente tu sais ce qu’est capable de faire ton objet. Dans tous les cas l’implémentation d’interface n’est pas obligatoire pour les classes et ne doit pas être systématique, cela répond just à un certain besoin, comme tout. Tu peux très bien composer tes objets avec l’injection de dépendances sans forcément utiliser d’interface. Une interface signifie juste que tu peux avoir différentes implémentations de celle ci.
Tu peux toujours te servir des mixins et des traits (je dis ça sans vouloir me focaliser sur un langage en particulier).
Non, il ne parle pas d’interface, il parle de composition vs héritage, tu peux très bien composer sans interface, c’est ce qu’il fait d’ailleurs. Normalement si tu as regardé la vidéo, t’aurais dû comprendre que justement « what you are » et « what you do » c’est pas la même chose… il le prouve assez bien pourtant.
Non, ce n’est pas vrai. Ce sont des choix de design. Encore une fois : hormis les limitations volontaires (et stupides) de Java et C#, il n’y a rien que tu puisses faire avec une interface que tu ne puisses pas avec une classe abstraite.
Dire ça, c’est essentiellement ne rien dire. Tu ne peux pas tenir une fonctionnalité pour responsable de ce que les devs peuvent en faire. C’est stupide de raisonner comme ça. Ça sert à rien de former des devs pour ne pas les laisser développer.
Non, ce n’est pas vrai. Encore une fois : il n’y a rien que tu puisses faire avec une interface que tu ne puisses pas avec une classe abstraite. Je ne vois pas en quoi utiliser une interface permette de mieux tester un objet. Tu peux aussi bien mocker une classe abstraite qu’une interface. Ce que tu dis n’est que du dogmatisme et ce n’est pas en le disant plus que ça en deviendra plus vrai.
Non, ça demande juste plus de code. Encore une fois : il n’y a rien que tu puisses faire avec une interface que tu ne puisses pas avec une classe abstraite.
Non, l’encapsulation consiste à exposer des méthodes sans avoir à te poser la question de la nature de l’objet ou de l’implémentation des méthodes. Comme l’interface n’implémente pas de méthode et, au moins en Java, demande un autre mot-clef pour l’étendre, t’es obligé de te demander si c’est une interface ou une classe et, quand tu travailles avec des interfaces dans une API, t’es obligé de trouver l’implémentation de l’interface qui va bien. Ça n’est pas une bonne pratique.
Si tu n’en implémentes pas les méthodes, alors tu es obligé de faire une classe abstraite. Sinon, tu dois fournir un implémentation, fusse-t-elle vide. CQFD.
Oui… Donc pas des interfaces…
Oui et moi, je parle de l’interface. Pas de la composition.
Non, il ne prouve rien. Et la preuve c’est que cette différence n’a aucun sens dans un langage à typage dynamique comme l’est le JS. Le VM ne s’occupe jamais du type des objets mais seulement de ce qu’ils savent faire id est si la méthode peut être appelée ou non. Et c’est bien la raison pour laquelle le JS te permet de changer le type d’un objet en pleine exécution en réaffectant
Object.prototype
Sérieusement, relis mieux l’article. Tout ce que tu m’opposes, j’y ai déjà répondu.
Dire qu’il n’y a rien que tu puisses faire avec une interface que tu ne puisses pas avec une classe abstraite c’est comme dire qu’il n’y a rien que tu puisses faire en procédurale que tu ne puisses faire en objet. Il y’a des milliards de possibilités pour arriver au même rendu, seulement y’a des gens qui ont inventé des best practices(S.O.L.I.D., Object calisthenics…), des designs patterns (IoC…) et des méthodes de développement (DDD, TDD, BDD…), libre à toi de les respecter ou non.
Donc oui, tu peux mocker une classe abstraite, même des méthodes privées à ce qu’il parait mais c’est pas ce que je ferai personnellement.
C’est la raison pour laquelle le titre est une question. Je n’ai pas titré : « Les interfaces sont-elles un mauvais patron de conception », mais « Les interfaces sont-elles un mauvais patron de conception ? » Et ça change tout. Je considère effectivement que l’interface a des cas d’utilisation très restraints et qui se limitent, dans la plupart des cas, à la conception d’API. Et même dans ces cas précis-là, ce qu’accomplit l’interface aurait pu être accompli avec une classe abstraite. Alors qu’on l’appelle interface ou classe abstraite, je m’en fout. Mais ce qui me dérange, c’est que l’interface ne soit là que parce que les concepteurs de Java et C# étaient trop fénéants pour faire de l’héritage multiple.
Et ça, c’est un problème.