Vos recrutements informatiques

700 000 développeurs, chefs de projets, ingénieurs, informaticiens...

Contactez notre équipe spécialiste en recrutement

Le changement de visibilité d'une fonction virtuelle est-il un viol du LSP ?

Le , par white_tentacle, Membre émérite
[Edit 3DArchi]
Suite à cette question : Accès à une méthode virtuelle protégée ou privée, un débat s'est ouvert pour savoir si le changement de visibilité d'une fonction virtuelle dans une classe dérivée est un viol du LSP.
Un des énoncés du LSP (ou Principe de Substituion de Liskov introduit par Barbara Liskov en 1987) peut être le suivant (cf ici ou ici):
Si pour chaque objet o1 de type S, il y a un objet o2 de type T tel que pour tous les programmes P définis avec le type T, le comportement de P reste inchangé lorsque o1 est substitué à o2 alors S est un sous-type de T.

La question initiale portait sur le changement de visibilité d'une fonction virtuelle dans la classe dérivée par rapport à la classe de base. Les intervenants s'accordent de reconnaitre que cette pratique est peu justifiable d'un point de vue conception, donc le débat ne porte pas la dessus.

La question posée dans cette discussion est :
Le changement de visibilité d'une fonction virtuelle est-il un viol du LSP ?

[/Edit]


Citation Envoyé par koala01  Voir le message
Le fait de redéfinir une fonction virtuelle en changeant sa visibilité est, surtout, une transgression grave du principe de subsitution de Liskov

En fait, non, j'ai beau retourner dans tous les sens, mais je ne vois pas en quoi ça viole le LSP, si on s'en tient à une approche objet.

Après, si on rajoute les génériques, ça devient différent. Mais comme ça sera résolu à la compilation, le LSP a moins de sens. C'est un peu bizarre d'avoir B polymorphique à A, tout en étant d'un concept (au sens c++1x) différent, mais j'ai du mal à trouver une bonne raison de l'empêcher (à peu près autant de mal qu'à y trouver un intérêt).


Vous avez aimé cette actualité ? Alors partagez-la avec vos amis en cliquant sur les boutons ci-dessous :


 Poster une réponse

Avatar de koala01 koala01 - Expert éminent sénior https://www.developpez.com
le 17/10/2010 à 13:10
Citation Envoyé par 3DArchi  Voir le message
Je me suis mal exprimé. Je considère l'expression this->do_something dans base::something. Et je substitue this de type base à un this de type derived. Je me retrouve pas loin du même cas que la question de départ

Mais réfléchit simplement à ce qui va se passer lorsque tu implémente l'idiôme NVI:

Tu as deux solutions:

Soit do_something a été réimplémenté dans la classe dérivée, et il est donc normal que ce soit l'implémentation propre à cette classe que tu appelle réellement.

Soit do_something n'a pas été réimplémenté, et tu utilisera donc l'implémentation de la classe de base.

Mais, quoi qu'il en soit, tu es au niveau du "fonctionnement interne" de la classe, et tu es déjà "au delà" du problème de la substituabilité qu'autorise LSP:

LSP n'est là "que" pour te permettre de déterminer s'il est cohérent de laisser l'utilisateur d'invoquer something au départ de la classe dérivée en faisant éventuellement passer l'objet pour une instance de la classe de base.

Si la réponse est oui, tu fais simplement un pas assez important en direction d'une décision d'héritage
Avatar de white_tentacle white_tentacle - Membre émérite https://www.developpez.com
le 18/10/2010 à 9:09
Je crois que le raisonnement de 3DArchi, avec lequel je suis d'accord, est le suivant :

- dans base::something, j'appelle, à cause du dispatch, derived::do_something, qui pourtant est private et que donc je ne pourrais normalement pas appeler. Mais je peux car virtual est indépendant de la visibilité, et que j'ai le droit d'appeler base::do_something

- dans le sujet de ce topic (changement de visibilité), j'appelle, à cause du dispatch, une fonction derived::f() private, donc que je ne pourrais normalement pas appeler. Mais je peux car virtual est indépendant de la visibilité, et que j'ai le droit d'appeler base::f().

Exprimé comme ça, la similitude ne te semble pas assez frappante ?
Avatar de 3DArchi 3DArchi - Rédacteur https://www.developpez.com
le 18/10/2010 à 12:28
Merci de reformuler mes pensées embrumées mais c'était exactement ça. Si je m'en tiens à une approche purement théorique du LSP, alors le NVI viole le LSP dès lors que la fonction virtuelle est privée.
Avatar de koala01 koala01 - Expert éminent sénior https://www.developpez.com
le 18/10/2010 à 14:04
Citation Envoyé par 3DArchi  Voir le message
Merci de reformuler mes pensées embrumées mais c'était exactement ça. Si je m'en tiens à une approche purement théorique du LSP, alors le NVI viole le LSP dès lors que la fonction virtuelle est privée.

Même pas...

LSP n'est absolument pas commutatif:

Il t'oblige à t'assurer que tu retrouve dans une classe dérivée toutes les propriétés de la classe de base, mais cela ne veut absolument pas dire que tu doive retrouver, dans la classe de base, toutes les propriété des classes dérivées

Cela n'aurait d'ailleurs aucun sens parce que cela invaliderait d'office une grande partie des relations d'héritage que l'on pourrait envisager

Si tu ne pouvais pas ajouter de comportement à un type dérivé par rapport à un type de base, la notion même de l'héritage n'aurait plus beaucoup de sens

Par contre, il faut effectivement admettre le fait que l'idiome NVI profite honteusement du "bug" qui empêche le compilateur de prendre l'accessibilité des fonctions virtuelles en compte... Et encore...

Je me doute bien que je ne couperai pas à une explication de cette remarque de normand, mais j'ai encore du mal à trouver l'expression claire du message que je veux faire passer...

Promis, ce sera pour une prochaine intervention
Avatar de metagoto metagoto - Membre éclairé https://www.developpez.com
le 19/10/2010 à 0:16
Citation Envoyé par koala01  Voir le message
LSP n'est absolument pas commutatif:

Il t'oblige à t'assurer que tu retrouve dans une classe dérivée toutes les propriétés de la classe de base, mais cela ne veut absolument pas dire que tu doive retrouver, dans la classe de base, toutes les propriété des classes dérivées

Dans LSP il y a Substitution. Il me parait hors sujet de parler de classe dérivée dans l'absolue.

Que le behavior du programme (ou la provable property) soit changé (infirmée) lorsque la classe dérivée est manipulée par son type statique ne nous intéresse pas ici. On est sensé raisonner uniquement dans le cadre d'une substitution.

Plusieurs programmes C++ proposés dans cette thread montrent clairement que le LSP n'est pas violé en ce qui les concerne.
Avatar de Flob90 Flob90 - Membre expert https://www.developpez.com
le 20/10/2010 à 23:21
Citation Envoyé par metagoto  Voir le message
Plusieurs programmes C++ proposés dans cette thread montrent clairement que le LSP n'est pas violé en ce qui les concerne.

En effet, si tu le vérifies avec le C++ alors en effet il n'est pas violé. (cf les exemple au début du sujet de test de substitution)

Mais si tu effectues la vérification avant de considérer le langage, tu ne peux pas tester la substitution (pas de possibilité de faire de code), d'où la nécessité de l'énoncé formel, qui n'est pas vérifié dans ce cas.

Et c'est justement, il me semble, ce que koala dit depuis le début, ne pas considérer le langage pour vérifier le LSP.

Après c'est évident que si tu n'es pas d'accord avec lui sur ce point tu peux arriver à une conclusion totalement différente.
Avatar de metagoto metagoto - Membre éclairé https://www.developpez.com
le 22/10/2010 à 7:52
Citation Envoyé par Flob90  Voir le message
Mais si tu effectues la vérification avant de considérer le langage, tu ne peux pas tester la substitution (pas de possibilité de faire de code), d'où la nécessité de l'énoncé formel, qui n'est pas vérifié dans ce cas.

Sans langage, sans implémentation, je pense qu'on ne peut rien dire du LSP, et plus particulièrement ce qui conduit à enfreindre le principe. On pourra tout au plus tabler sur le fait que le langage choisi in fine supportera une certaine notion de "substitution".

Je suis d'accord qu'un changement de visibilité est suspect et probablement une mauvaise idée, mais comme on l'a vu, cela ne veut pas dire pour autant que le LSP est violé quelque soit le langage et quelque soit le programme.
Avatar de white_tentacle white_tentacle - Membre émérite https://www.developpez.com
le 22/10/2010 à 12:29
Citation Envoyé par Flob90  Voir le message
Mais si tu effectues la vérification avant de considérer le langage, tu ne peux pas tester la substitution (pas de possibilité de faire de code), d'où la nécessité de l'énoncé formel, qui n'est pas vérifié dans ce cas.

Ce n'est pas le langage qui est important, c'est la manière dont va se faire la substitution. Ceci peut dépendre du langage, mais pas forcément (il me semble que VB.net et C# ont les mêmes règles de substitution, par exemple), et dans tous les cas il est possible de créer un autre langage qui utilisera les mêmes règles.

Après c'est évident que si tu n'es pas d'accord avec lui sur ce point tu peux arriver à une conclusion totalement différente.

Je crois que c'est une des raisons des divergences d'opinion, oui, on ne parle pas de la même chose .
Avatar de koala01 koala01 - Expert éminent sénior https://www.developpez.com
le 22/10/2010 à 13:46
Hé bien, commençons par mettre les choses à plat, si tu le souhaite, et par dire exactement de quoi on parle...

On parle, tout le monde sera d'accord avec cela, des langages (orientés) objets.

De même, je présumes que tout le monde sera d'accord pour dire que la substitution est la mise en oeuvre "pratique" d'une caractéristique "théorique" appelée la "substituabilité".

La substituabilité est cette caractéristique qui permet, dans les langages (orientés) objets, d'invoquer une fonction qui attend un type donné d'objet en lui passant un objet d'un autre type sans devoir recourir à la conversion, et en étant sur d'observer un comportement adapté au type réel de l'objet transmis.

En d'autres termes, la substituabilité est la caractéristique qui permet de profiter, grâce à l'héritage (publique) qui peut exister entre deux types distincts, du polymorphisme.

Je pense que, jusque là, nous parlons bel et bien de la même chose, autrement, il serait intéressant que tu nous dise de quoi tu parles

Or, tu constatera que, bien que la manière dont je définis la substituabilité est, en définitive, fort proche de ce que nous dit LSP:
If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T.

Que je traduis, par bonté d'âme:
Si pour chaque objet o1 de type S il y a un objet o2 de type T tel que pour tout programme P défini en terme de T, le comportement de P est inchangé lorsque l'on substitue o1 à o2, alors S est un sous type de T

Tu remarqueras:
  1. Que l'on ne fait aucun cas du langage utilisé
  2. Que le principe ne donne aucune exception à la règle... Or, si une règle souffre d'exception, elles seront énoncées en même temps que la règle (ex: la règle pour déterminer si une année est bissextile est composée d'une règle générale (divisible par 4), d'une exception (pas divisible par100) et d'une exception à l'exception (mais divisible par 400) ). On doit donc considérer la règle telle qu'elle est énoncée comme complète.
J'ai juste un problème avec le terme "inchangé" de l'énoncé, que j'aurais volontiers remplacé par "cohérent" (sans doute parce que je ne suis pas gêné par le fait de l'appliquer en dehors du cadre pour lequel il a été énoncé : le cadre de la programmation par contrat ) , ce qui augmente déjà la portée dans laquelle on peut considérer que le principe est respecté

Mais, même sorti du cadre de la programmation par contrat (auquel cas il faut, si on veut retourner dans ce cadre particulier, appliquer les règles qui le régissent séparément), le langage n'a absolument pas à intervenir dans la validation du respect du principe.

Je vais prendre un exemple pour te faire comprendre: Mettons que l'on veuille créer une classe TurboGenerator.

Comme il s'agit d'une turbine couplée à un générateur (ou est-ce un générateur couplé à une turbine ), que l'on peut, en tout cas, utiliser le turbo générateur aussi bien comme une turbine que comme un générateur, LSP nous dit que TurboGenerator est un sous type de l'un comme de l'autre.

En C++, il n'y aurait strictement aucun problème: l'héritage multiple est tout à fait autorisé. On peut donc tout à fait considérer que LSP est parfaitement respecté

Mais, si, juste à coté, tu as quelqu'un qui programme en java, qui n'autorise pas l'héritage multiple, il va te dire qu'il ne peut pas faire hériter TurboGenerator à la fois de Turbin et de Generator...

Il sera obligé de faire hériter TurboGenerator de Turbin et de lui faire implémenter l'interface de Generator, ou, à l'inverse de faire hériter TurobGenerator de Generator et de lui faire implémenter l'interface de Turbin.

Pourtant, quelle que soit la solution qu'il choisira, l'autre solution continuera parfaitement à respecter pleinement LSP, et si java acceptait l'héritage multiple, la solution de faire hériter TurboGenerator de Turbin ET de Generator le respecterait aussi.

Ce qu'il faut comprendre, c'est que, s'il faut respecter, lors de la conception, les restrictions imposées par le langage utilisé et qu'il faut, aussi, respecter les décisions de conception qui ont été prises par ailleurs (le javaiste devra prendre en compte la décision qui aura été prise de transformer Turbin ou Generator en interface), LSP reste tout à fait "au dessus de la mêlée", car, s'il valide une décision (ou du moins qu'il ne l'invalide pas), la décision restera valide pour n'importe quel langage (sous réserve toutefois d'en respecter les restrictions).

Par contre, s'il invalide une décision prise, le fait que le langage autorise à ne pas prendre cette invalidation en compte ne devrait en aucun cas t'inciter à poursuivre dans cette voie, ne serait-ce que parce que, au final, tu te retrouvera avec des comportements incohérents (ex: le fait que tu puisse invoquer une fonction qui est normalement inaccessible depuis un objet donné "simplement" parce que tu as été "assez bête" pour te mettre dans une situation dans laquelle tu as "oublié" son type réel).
Avatar de Emmanuel Deloget Emmanuel Deloget - Expert confirmé https://www.developpez.com
le 24/11/2010 à 11:48
Citation Envoyé par pseudocode  Voir le message
"What is wanted here is something like the following substitution property: If
for each object o1 of type S there is an object o2 of type T such that for all
programs P defined in terms of T, the behavior of P is unchanged when o1 is
substituted for o2 then S is a subtype of T."

(Barbara Liskov, “Data Abstraction and Hierarchy", 1988)

Restons pragmatique et ne donnons pas au LSP des super-pouvoirs qu'il ne devrait pas avoir.

Si Liskov était si fière de cette définition de 1988, elle n'en aurait pas proposé une autre 6 ans plus tard Notamment, cette de 1994 est beaucoup plus stricte que celle de 1988, qui ne se base que sur le comportement substitué (comportement, au sens contrat). La définition usuelle courante parle de propriétés - dont, grosse différence, le comportement ne fait pas partie. Ces propriétés, je les ai cité dans un post précédent, je les liste à nouveau ici :

* accès
* pré- et post-conditions

L'accès impose bien évidemment l'existence.

LSP a effectivement des super-pouvoirs: grâce à ce principe, on sait si une classe peut dériver d'une autre. C'est quand même un point particulièrement important dans le domaine de l'architecture logicielle

Outre ces points; il semblerait que certains d'entre vous continuent de mélanger "ce qu'il est possible de faire" et "ce qui doit être fait", en étant fortement influencé par les langages majeurs de notre génération. Les deux notions sont très différentes - il suffit d'installer eMule pour commencer à télécharger des films, donc c'est tout à fait possible. Est-ce que c'est ce qui doit être fait (au regard de la loi; vos opinions personnelles ont peu d'intérêt dans ce cas précis ).

Ce qui doit être fait, c'est respecter LSP de manière correcte, y compris les notions d'accès, pré-condition et post-condition. Ce qui peut être fait, c'est passer outre certaines restrictions de LSP en se servant de certaines faiblesses des langages usuels.

La pratique est plus permissive que la théorie, mais ça ne veut pas dire que la théorie accepte la pratique. Ce qui ne veut pas dire, en retour, que la pratique est condamnable : il y a des cas légitimes ou je peux souhaiter par exemple que toutes les fonctions de ma classe dérivée soit privées - Je peux l'instancier, mais je ne peux pas utiliser ses services. Je ne peux pas non plus l'étendre en la dérivant. J'impose une limitation technique, afin de contrôler au mieux mon design. C'est, en soi, une bonne chose. Ca reste une violation du LSP, mais (comme je l'ai dit auparavent) on conçoit des systèmes en s'appuyant sur des langages, et on utilise les possibilités de ces langages à notre avantage.

Comme tout principe, comme toute règle, le LSP a une certaine souplesse. De la même manière qu'on peut violer une règle en motivant cette violation, LSP accepte d'être violé à condition que le résultat ait un intérêt fort. Il n'en reste pas moins violé, mais au moins, on sait pourquoi on le fait, et ce que ça nous apporte.

Pour résumer : ce n'est pas parce que le pragmatisme s'éloigne un poil de la théorie que le pragmatisme a tord ou que la théorie a besoin d'être étendu. Comme le dit le célèbre adage, la différence entre la théorie et la pratique, c'est qu'en théorie, c'est la même chose.
Avatar de koala01 koala01 - Expert éminent sénior https://www.developpez.com
le 24/11/2010 à 21:06
Citation Envoyé par Emmanuel Deloget  Voir le message
Comme tout principe, comme toute règle, le LSP a une certaine souplesse. De la même manière qu'on peut violer une règle en motivant cette violation, LSP accepte d'être violé à condition que le résultat ait un intérêt fort. Il n'en reste pas moins violé, mais au moins, on sait pourquoi on le fait, et ce que ça nous apporte.

Tout à fait, mais, à défaut d'avoir une justification cohérente et qui tient la route (non: la justification du "le langage le permet n'entre pas dans cette catégorie !!!), la violation ne peut absolument pas être envisagée.

Que l'on envisage de contrevenir à LSP ou à n'importe quelle règle à un moment particulier, dans une situation tout à fait particulière et parce que l'on est en mesure de justifier pourquoi on le fait est cependant une chose très éloignée d'une discussion "générale" comme celle ci
Offres d'emploi IT
Ingénieur analyste programmeur (H/F)
Safran - Auvergne - Montluçon (03100)
Architecte électronique de puissance expérimenté H/F
Safran - Ile de France - Villaroche - Réau
Ingénieur conception en électronique de puissance H/F
Safran - Ile de France - Moissy-Cramayel (77550)

Voir plus d'offres Voir la carte des offres IT
Contacter le responsable de la rubrique C++