Envoyé par
metagoto
Oui bien sur, on parle de hiérarchie a N niveaux.
Certes, mais tout dériver de Object ne suffit pas à dire qu'on ne respecte pas le LSP. C'est ça le coeur de mon interrogation.
Après, je ne dis pas qu'une telle hiérarchie est meilleur qu'une autre.
Attention, je tiens à préciser que je n'ai jamais dit qu'une hiérarchie basée sur une classe de base unique ne respectait pas LSP...
J'ai bien dit (c'était le sens de ma phrase) qu'une telle hiérarchie prenait, à mon sens, certaines libertés par rapport à LSP.
Et, justement, je me disais que je n'étais pas vraiment allé jusqu'au bout de mon raisonnement... et tu me donne une occasion "unique" de le poursuivre
Nous sommes donc d'accord sur ce qu'implique LSP, et sur le fait qu'il s'applique aux N niveaux d'héritage
Or, le seul ensemble de "propriétés communes" (c'est tout le sens de LSP) que l'on puisse trouver afin de décider (en respectant LSP) de créer une classe ancêtre commune à toutes les autres est... un ensemble vide.
Autrement dit, la classe Object (ou quelle que soit son orthographe) ne fournirait aucun service serait composée plus ou moins comme suit:
1 2 3 4 5 6 7 8 9 10
| class Object
{
public:
/* même pas besoin de définir un constructeur, celui fournit par
* le compilateur suffit
*/
virtual ~Object() /* =0 si on veut s'assurer que la classe en soit
* pas instanciée en l'état
*/;
}; |
Si tu y réfléchi, tu ne peux simplement rien faire de l'objet, à par décider de le détruire (tu me dira peut être que ce n'est déjà pas "si mal"
).
Mais la conséquence directe, c'est que tu ne peux pas envisager d'utiliser concrètement l'instance d'une classe dérivée de Object sans... passer par un downcast...
Or, c'est là que le bât blesse: si j'admet que le downcast est *parfois* intéressant / utile / nécessaire, j'estime qu'il est mauvais de forcer l'utilisateur à l'utiliser de manière systématique car, dans certains cas, cela force l'utilisateur à mettre en place, par exemple, un pattern "chaine de responsabilités", et donc à complexifier le desing de l'application bien plus que ce qu'il aurait été nécessaire si l'héritage de Object n'avait pas été effectué (forçant l'utilisateur à créer une collection d'objets pris dans le sens moins "générique"
.
De plus, l'héritage d'une classe commune présente un autre effet de bord, assez particulier: celui de "déporter" une série de responsabilités.
Imaginons une collection d'objets.
Normalement, la collection est sensée savoir quel type d'objet concrets elle doit accepter et renvoyer:
La responsabilité d'accepter d'ajouter un objet à la collection sur base de la cohérence de type devrait, normalement, échoir à... la collection...
De même, lorsque l'on accède à un élément de la collection, nous sommes en droit d'obtenir un objet, à tout le moins, utilisable...
Or, si on crée une collection de Object, non seulement, nous disons à la collection "tu peux tout accepter, y compris de mettre une pomme à coté d'une voiture", mais, en plus, nous déportons la responsabilité de cohérence de type vers la fonction qui devra manipuler la collection... A charge de l'utilisateur de veiller à ce que les types manipulés lors de l'insertion ou de la récupération d'éléments soit cohérent.
A l'insertion, nous pouvons assurer une certaine cohérence de type en décidant d'insérer non pas des instances de Object, mais plutôt des instances de VehiculeAMoteur (incluant certains bateaux, des camions, des voitures et... des trottinettes à moteurs)
A l'utilisation de la collection, ou bien nous nous reposons sur le fait que l'insertion a assuré une certaine cohérence des données (... en espérant que c'est bien le cas), ou bien, nous préférons quand même vérifier que le downcast a réussi, mais, quoi qu'il en soit, nous devons downcaster l'Object récupéré en, par exemple, VehiculeAMoteur, afin de pouvoir "ratisser large" et manipuler certains bateaux, des camions, des voitures ou... des trottinettes à moteurs...
Au passage, je signale qu'il y aurait sans doute également un problème de conception si, l'on décidait de manipuler une collection d'instances de type VehiculeAMoteur alors que l'on souhaite limiter le type aux seules voitures car une même cause aura les même effets: nous devrons downcaster chaque élément avant de pouvoir l'utiliser
Or, justement, LSP a, entre autre, comme conséquence de s'assurer que, quel que soit le type parent que tu décidera de prendre comme base lorsque tu crée une collection, tu puisse utiliser l'interface publique de ce type pour manipuler les éléments de cette collection sans "te poser de question"...
Et il faut avouer que, non seulement, il n'est pas cohérent de permettre (comme le fait la classe Object) de placer des pommes et des voitures dans une même collection, mais, en plus, que l'interface publique de la classe Object est - définitivement - trop légère pour être d'une quelconque utilité.
Voilà en quoi j'estime que la philosophie du "tout objet" prend des liberté par rapport à LSP.
1 |
0 |