Mais, comme une propriété, quand elle est associée à une fonction est "est capable de réagir à l'appel de la dite fonction", il est tout à fait logique de ne définir le comportement que lorsque l'on peut effectivement le faire.
Là, je ne suis pas d'accord:Non. On n'invoque pas un comportement. On invoque une méthode. Et le fait d'être en mesure d'invoquer une méthode ca ne valide rien du tout, a part que le code compile correctement.
C'est le fait de permettre d'invoquer un fonction donnée qui correspond (ou de faire, pourquoi pas, appel "à quelque chose d'accessible depuis l'extérieur d'un type donné") qui est une propriété valide:
Tant que tu peux "faire appel à cette chose accessible depuis l'extérieur", la propriété associée est validée.
Le fait que "cette chose accessible depuis l'extérieur" soit une fonction, que le comportement de cette fonction soit adapté (comprend: tout en restant cohérent avec ce que l'on attend du type réellement utilisé lorsque l'on appelle le dit comportement), ou que le résultat de ce comportement soit différent n'invalide absolument pas la propriété qui reste "d'être capable d'accéder" à ce qui est associé à cette propriété
Dans un contexte donné qui est la programmation par contrat.Le fait que l'implémentation de la méthode respecte le "contrat" du comportement de la classe, ça oui, ca valide le comportement.
Mais il n'est absolument pas question de ce contexte particulier dans l'énoncé de LSP: il te dit que, si tu peux faire appel à une propriété depuis l'objet de base, tu dois pouvoir faire appel à cette propriété depuis un objet dérivé.
Je suis d'accord avec le fait que l'adaptation d'un comportement ou d'une propriété devrait respecter les invariants, mais cela n'intervient absolument pas dans LSP...
Les deux seules choses que LSP impose sont:
Que tu puisse invoquer un comportement du type de base au départ de n'importe quel objet de type dérivé
Que le comportement invoqué reste "cohérent" par rapport à ce que l'on est en droit d'attendre (comprend: que addChild ne finisse pas par... retirer un objet, par exemple )
Enrichir ou adapter...Et ce que dit le LSP, c'est qu'un comportement existant ne doit pas être supprimé lors d'un héritage. On peut éventuellement ajouter ou enrichir les comportements, mais pas les supprimer.
Tout dépend du sens que tu donnes au terme "enrichir".
En effet, dans le cadre de la programmation par contrat, tu peut être confronté au non respect des invariants, des pré et post conditions. Mais, comme je l'ai dit, cela n'a rien à voir avec LSP
Par contre, je remarque avec plaisir que, pour en revenir au terme du débat, le fait de supprimer un comportement (en changeant l'accessibilité d'une fonction déclarée comme publique au niveau de la classe de base) viole effectivement LSP, vu que tu admets que l'on ne peut pas supprimer le dit comportement
A condition que tu dispose, au niveau de l'interface, des données qui te permettent de fournir l'implémentation cohérente du résultat.Le fait qu'en C++ (java, c#) on hérite automatique des méthodes publiques, ca implique alors que les méthodes publiques ne doivent être utilisées uniquement que pour décrire des comportements. C'est d'ailleurs une bonne pratique en Java/C# de passer par la définition d'interface pour spécifier des comportements et de les faire hériter à la classe.
En C++, la notion d'interface n'existe pas "en tant que telle", et on remarque qu'il n'y a pas vraiment de différence au niveau de la conception entre la création d'une interface et celle d'une fonction.
De plus, java indique bel et bien le fait que l'utilisation d'une interface n'est pas un héritage vu qu'il utilise le mot clé "implements":
La notion de ce mot clé est beaucoup plus proche de la relation "est implémenté en terme de" que de la relation "est un objet pouvant passer pour" (la fameuse relation EST-un )
Comme je l'ai dit, il n'y a pas de sens à vouloir appliquer un principe théorique spécifique à la conception objet en dehors d'un contexte objetMais comme je l'ai dit, tout cela n'a de sens que si on reste dans un model de conception orienté objet.
L'héritage dans le but de réutilisation de code n'a pas *forcément* de sens au niveau LSP et est, généralement, déconseillé...Si on utilise le "mécanisme" d'héritage pour ce qui est somme toute du templating ou de la réutilisation de code, tout cela ne s'applique pas.
Je te rappelle que tu n'a pas forcément de relation de hiérarchie entre du spécialisations données d'une seule et même classe template
Absolument pas: on entre dans le cadre du respect de LSP car, pour tout sous type de Base, il est possible d'invoquer la fonction addChild indépendamment du type réellement utilisé.Et ton exemple de base/node/leaf entre a mon sens dans cette catégorie.
De plus, tu remarquera qu'il n'y a strictement aucune récupération de code, même si j'aurais effectivement pu écrire le code sous la forme de
Mais la différence n'apparait au final qu'au niveau de l'implémentation de ce que aura conçu "par ailleurs"
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 class Base { public: virtual Base() = 0; /* il faut bien interdire la création d'une instance * de Base ;) */ virtual void addChild(Base*){throw CantHaveChildren();} }; class Leaf : public Base { /* tous les comportements définis dans base sont exatement ceux * que l'on peut attendre pour une instance de Leaf */ }; class Node : public Base { public: /* adaptation du comportement à un type qui peut, effectivement, * disposer d'enfants */ virtual void addChid(Base * b) { children_.push_back(b); } private: std::vector<Base*> children_; };
Partager