IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Langage C++ Discussion :

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


Sujet :

Langage C++

  1. #1
    Membre émérite
    Avatar de white_tentacle
    Profil pro
    Inscrit en
    Novembre 2008
    Messages
    1 505
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2008
    Messages : 1 505
    Points : 2 799
    Points
    2 799
    Par défaut Le changement de visibilité d'une fonction virtuelle est-il un viol du LSP ?
    [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).

  2. #2
    En attente de confirmation mail

    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2004
    Messages
    1 391
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 34
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Points : 3 311
    Points
    3 311
    Par défaut
    Si tu réduis la visibilité il est clair que u violes le LSP, le LSP dit que toute propriété valide sur une mère l'est sur une fille, si tu réduis l'interface, tu rends invalide ceci (la propriété : "peut envoyer le message xxx" n'est pas valide).
    Par contre si tu l'augmentes, ca ne l'invalide pas. Mais ca reste assez illogique de le faire, une méthode privé virtuel permet de ne définir qu'un comportement. Si tu passes de privé (ou protégé) à publique, tu choisies délibérement de faire de cette fonction un élément de l'interface alors que le concepteur de la classe mère avait déterminé qu'elle ne devait pas en faire partie (peut-etre pour respecter le SRP ?).
    J'ai du mal à voir ce qui pourrait motiver le choix de faire d'un élément qui ne fait que définir un comportement, un élément de l'interface.

  3. #3
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 629
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 629
    Points : 30 692
    Points
    30 692
    Par défaut
    Comme j'arrive un peu tard, je ne peux qu'appuyer les écrits de Flob90...

    La base du raisonnement de Liskov, c'est que toute propriété (comprend: la capacité de recevoir, d'émettre ou de répondre (à) un message donné) valide pour la classe de base doit être valide pour toute classe dérivée, de manière directe ou indirecte.

    Cela sous entend que si la classe A a une propriété XXX qui est valide pour A, la propriété devra aussi être valide pour toute classe qui dérive de A, mais aussi pour toute classe qui dérive d'une classe qui dérive d'une (on pourrait continuer 106 ans comme cela) classe qui dérive de A.

    Si tu réduis la visibilité d'une fonction prévue dans l'interface publique de la classe mère, tu essaye d'invalider la propriété en question: Tu ne peux en effet pas envisager sereinement de placer une instance de la classe dérivée dans une collection manipulant (des pointeurs sur) la classe de base, car tu cours le risque que l'utilisateur fasse appel, en toute bonne fois, à la propriété en question.

    A l'inverse, si une fonction est protégée ou privée dans la classe de base, elle n'est là que pour faciliter le travail des autres fonctions membres de la classe, voire le travail des fonctions membres des classes dérivées.

    Elle n'est donc qu'à "usage interne" en quelque sorte, et fait littéralement partie de ce que l'on pourrait appeler les "détails d'implémentation".

    Il n'y a donc aucune raison pour exposer cette fonction au tout publique dans les classes dérivées

  4. #4
    Membre émérite
    Avatar de white_tentacle
    Profil pro
    Inscrit en
    Novembre 2008
    Messages
    1 505
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2008
    Messages : 1 505
    Points : 2 799
    Points
    2 799
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Si tu réduis la visibilité d'une fonction prévue dans l'interface publique de la classe mère, tu essaye d'invalider la propriété en question: Tu ne peux en effet pas envisager sereinement de placer une instance de la classe dérivée dans une collection manipulant (des pointeurs sur) la classe de base, car tu cours le risque que l'utilisateur fasse appel, en toute bonne fois, à la propriété en question.
    Sauf que la fonction est toujours appelable via l'interface de A, et que donc le LSP n'est pas malmené de ce point de vue. Tu peux tout à fait le mettre dans une collection, car l'appel à la propriété en question fonctionnera parfaitement (même s'il ne fonctionnerait plus aprés downcast).

    Non, le seul cas qui pose problème par rapport au LSP, c'est l'utilisation de B comme paramètre d'une classe template. Mais fondamentalement, je ne crois pas que le LSP ait été prévu pour ça, ça sera détecté à la cmnpilation donc ça ne me semble pas un souci autre mesure.

  5. #5
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 629
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 629
    Points : 30 692
    Points
    30 692
    Par défaut
    Citation Envoyé par white_tentacle Voir le message
    Sauf que la fonction est toujours appelable via l'interface de A, et que donc le LSP n'est pas malmené de ce point de vue. Tu peux tout à fait le mettre dans une collection, car l'appel à la propriété en question fonctionnera parfaitement (même s'il ne fonctionnerait plus aprés downcast).
    C'est justement parce que la fonction est accessible publiquement depuis l'interface de A que LSP est malmené...

    Vu que la fonction ne devrait pas être accessible publiquement pour la classe dérivée.

    Comprend moi: Si tu n'a une vision que de la classe dérivée, en tant qu'utilisateur, tu ne vas jamais tenter d'appeler une fonction protégée ou privée depuis l'extérieur, vu que tu t'attend à avoir un message proche de "foo is private / protected in this context".

    A l'inverse, si tu n'a qu'une vision de la classe de base, toujours en tant qu'utilisateur de celle-ci, tu envisagera sans problème d'appeler la fonction, vu qu'elle est... publique, et fait donc partie de l'interface dont tu dispose pour interagir avec ton objet.

    Quel que soit le sens du point de vue où tu te place, tu as donc une incohérence du seul fait que tu peux faire quelque chose d'un coté que tu ne peux pas faire de l'autre.

    LSP n'est donc, purement et simplement, pas respecté.

    En gros, l'héritage (public) peut ajouter des possibilités à l'interface de base, mais pas en supprimer

  6. #6
    Membre émérite
    Avatar de white_tentacle
    Profil pro
    Inscrit en
    Novembre 2008
    Messages
    1 505
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2008
    Messages : 1 505
    Points : 2 799
    Points
    2 799
    Par défaut
    Citation Envoyé par koala01 Voir le message
    C'est justement parce que la fonction est accessible publiquement depuis l'interface de A que LSP est malmené...
    Pas vraiment, non. B reste substituable à A, tant que l'on considère que c'est un A. Ca donne une situation un peu étrange, où une instance de B est toujours substituable à une instance de A, mais où le type B n'est plus substituable au type A (d'où le fait que ça ne marche pas pour les génériques).

    Comprend moi: Si tu n'a une vision que de la classe dérivée, en tant qu'utilisateur, tu ne vas jamais tenter d'appeler une fonction protégée ou privée depuis l'extérieur, vu que tu t'attend à avoir un message proche de "foo is private / protected in this context".
    Tout à fait d'accord.

    A l'inverse, si tu n'a qu'une vision de la classe de base, toujours en tant qu'utilisateur de celle-ci, tu envisagera sans problème d'appeler la fonction, vu qu'elle est... publique, et fait donc partie de l'interface dont tu dispose pour interagir avec ton objet.
    Là aussi je suis d'accord.

    Quel que soit le sens du point de vue où tu te place, tu as donc une incohérence du seul fait que tu peux faire quelque chose d'un coté que tu ne peux pas faire de l'autre.
    Si, je peux des deux côtés. Ce que tu occultes, c'est que l'interface d'une classe ne se restreint pas à ce qu'elle définit, mais aussi à tout ce dont elle hérite. Et donc, si j'ai la chose suivante :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    class A {
    public:
      virtual void f();
    };
    class B: public A {
    protected:
      virtual void f();
    }
    L'interface de B contient toujours ce public A::f() (la preuve : on peut l'appeler ).

    LSP n'est donc, purement et simplement, pas respecté.
    LSP est parfaitement respecté sur les instances des objets. Le problème se pose pour les types eux-mêmes, mais le LSP ne s'intéresse qu'aux instances.

    En gros, l'héritage (public) peut ajouter des possibilités à l'interface de base, mais pas en supprimer
    Il ne la supprime pas vraiment, puisqu'elle reste disponible si tu utilises l'interface de base. Partout où tu utilises une instace de A, tu peux utiliser une instance de B à la place. C'est en ce sens là que pour moi, le LSP n'est pas malmené.

    Par contre, je n'ai toujours pas compris à quoi ça peut servir

  7. #7
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Points : 13 017
    Points
    13 017
    Par défaut
    Salut,
    Citation Envoyé par white_tentacle Voir le message
    Pas vraiment, non. B reste substituable à A, tant que l'on considère que c'est un A. Ca donne une situation un peu étrange, où une instance de B est toujours substituable à une instance de A, mais où le type B n'est plus substituable au type A (d'où le fait que ça ne marche pas pour les génériques).
    J'ai tendance à penser comme toi. C'est bien la différence entre une instance substituable à une autre dans un expression qui reste valide mais la substitution de type non.
    En passant de l'énoncé théorique à sa pratique concrète dans une expression C++, cela implique qu'on parle d'une expression dans laquelle l'instance de A est présente par référence ou pointeur permettant d'avoir toujours le même type statique (A) et un type dynamique différent (B) devant supporter le LSP.
    Si l'instance de A est présente par valeur, il faudrait mieux préciser ce que l'on entend par substituable : soit cela implique une copie de B en A (beurk) donc rien n'a vraiment été substitué dans l'expression, soit cela implique que l'expression reste valide avec un type B au quel cas cela n'est plus substituable, soit cela ne concerne pas les instances dont les types statiques et dynamiques ne peuvent être différents ce que j'ai tendance à penser (soit je mélange tout ).
    Ceci dit, c'est un choix du C++ d'avoir indiqué que le contrôle d'accès à une fonction virtuelle était fait au moment de la compilation sur le type statique. Si demain on choisissait de faire un contrôle à l'exécution sur le type dynamique (peut être cela existe-t-il dans d'autres langages objets ?), alors le LSP ne pourrait plus être respecté si on change la visibilité.

    Citation Envoyé par white_tentacle Voir le message
    L'interface de B contient toujours ce public A::f() (la preuve : on peut l'appeler ).
    Que dans une expression utilisant A comme type statique :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    A a;
    a.f(); // ok
    B b;
    b.f(); // erreur
    A &rb = b;
    rb.f(); // ok

    Citation Envoyé par white_tentacle Voir le message
    Par contre, je n'ai toujours pas compris à quoi ça peut servir
    Moi non plus . Si on peut légitimement penser que LSP est respecté, je pense qu'on peut tout aussi légitimement penser qu'il y a (au moins dans 90% des cas) un problème de conception si on commence à changer la visibilité d'une fonction virtuelle dans les classes dérivées.

  8. #8
    En attente de confirmation mail

    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2004
    Messages
    1 391
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 34
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Points : 3 311
    Points
    3 311
    Par défaut
    Ha, j'avais pas fait attention que la visibilité était évaluée de manière statique (j'avais testé en plus, j'ai du me tromper en tapant ...).

    Du coup, en prenant la définition, "on peut utiliser une fille lorsque l'on attend une mère", c'est valide. Mais en prenant la définition formel avec les propriétés, ca reste bel et bien faux. La propriété "peut recevoir le message xxx", est vraie pour la mère mais pas pour la fille. Sauf dans le cas où la fille est de type statique celui de la mère, mais je ne crois pas que Mme Liskov fasse la distinction de typage statique/dynamique, dans son principe (du moins je ne me souvient pas en avoir vu une). Ou alors ce sont les mécanismes du C++ qui font que l'on doit considérer cette distinction dans l'énoncé ?

    Pour l'utilité, j'avoue que je ne la voie pas non plus, si tu en arrives à faire cà, j'ai l'impression que tu cherches à redéfinir et utiliser le comportement de la classe mère mais sans "l'afficher", ce qui correspondrait plutot à un héritage privé/protégé. (être implémenté en terme de ...)

  9. #9
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 629
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 629
    Points : 30 692
    Points
    30 692
    Par défaut
    Je suis désolé, j'ai été absent presque toute la journée...
    Citation Envoyé par white_tentacle Voir le message
    L'interface de B contient toujours ce public A::f() (la preuve : on peut l'appeler ).
    Justement, c'est encore pis...

    Tu imagines un peu!!! tu dis "attention, ce comportement est à usage interne", en définissant la fonction dans une accessibilité protégée ou privée, mais, d'un autre coté, tu autorise l'invocation d'un comportement qui, dans le meilleur des cas est incomplet et qui, dans le pire des cas est carrément erroné, vu qu'on a eu besoin de le redéfinir pour B...

    Si encore la fonction était publique dans la classe dérivée, je ne dis pas, mais, quand on fait passer la fonction dans une accessibilité restreinte...

    Vise directement la tête plutôt que le pied, ce sera plus douloureux, mais ca durera moins longtemps

    Citation Envoyé par 3DArchi Voir le message
    Salut,
    J'ai tendance à penser comme toi. C'est bien la différence entre une instance substituable à une autre dans un expression qui reste valide mais la substitution de type non.
    En passant de l'énoncé théorique à sa pratique concrète dans une expression C++, cela implique qu'on parle d'une expression dans laquelle l'instance de A est présente par référence ou pointeur permettant d'avoir toujours le même type statique (A) et un type dynamique différent (B) devant supporter le LSP.
    Si l'instance de A est présente par valeur, il faudrait mieux préciser ce que l'on entend par substituable : soit cela implique une copie de B en A (beurk) donc rien n'a vraiment été substitué dans l'expression, soit cela implique que l'expression reste valide avec un type B au quel cas cela n'est plus substituable, soit cela ne concerne pas les instances dont les types statiques et dynamiques ne peuvent être différents ce que j'ai tendance à penser (soit je mélange tout ).
    Le problème, c'est que la substituabilité est très fortement liée avec le (j'irais jusqu'à dire que c'est un pendant du) polymorphisme: c'est parce que l'on a la substiuabilité que le polymorphisme devient possible.

    Or, le polymorphisme est évalué... à l'exécution, et c'est parce que c'est le type dynamique et non le type statique de l'objet qui est pris en compte que tout le système fonctionne.

    Tu dois donc t'assurer que les propriétés de l'objet statique soient valide pour... le type dynamique de l'objet.

    Autrement, tu commence sérieusement à jouer à "faites ce que je dis, pas ce que je fais":

    D'un coté, tu fais clairement comprendre à l'utilisateur de la classe dérivée que la fonction c'est "touche pas à ca p'tit con", parce que réservé "à un usage interne", mais de l'autre, cela revient, quand l'objet en question est "noyé dans la masse" à lui dire "écoute, tu n'aurais normalement pas du appeler cette fonctions, mais comme tu a (été assez idiot pour perdre le type réel ) perdu le type réel de l'objet, et que tu as une bonne tête, je vais accepter d'effectuer l'action que tu me demande"

    Avec toute la meilleure volonté du monde, je ne cautionnerai jamais un tel comportement

    PS: avant que vous ne vous mettiez à crier harro sur moi-même, vous aurez compris que les petites phrases piquantes sont principalement de l'humour, et que je ne vise personne en particulier

  10. #10
    En attente de confirmation mail

    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2004
    Messages
    1 391
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 34
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Points : 3 311
    Points
    3 311
    Par défaut
    En gros c'est un peu comme les paramètre par defauts pour les méthodes virtuels, on peut le faire mais vaut mieux pas.

    Le mélange d'élément statique (la visibilité ou les parmètres par defaut), avec un système dynamique (la virtualité), fait mauvais ménage, risque de comportement innatendue (même si prévisible).

  11. #11
    Expert confirmé

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2007
    Messages
    1 895
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 48
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Septembre 2007
    Messages : 1 895
    Points : 4 551
    Points
    4 551
    Par défaut
    Citation Envoyé par 3DArchi Voir le message
    Salut,
    J'ai tendance à penser comme toi. C'est bien la différence entre une instance substituable à une autre dans un expression qui reste valide mais la substitution de type non.
    L'énoncé formel du LSP est (traduit par mes soins) : Soit q(x) une propriété prouvable à propos des objets x de type T. Alors q(y) doit être vrai pour les objets y de type S lorsque S est un sous-type de T. Voir http://blog.emmanueldeloget.com/inde...tion-de-liskov pour plus d'infos.

    Pour démontrer que le code présenté ci-dessus viole LSP, on pose juste q(x)=vrai si la fonction f() de x est publique pour tout x instance de A. Le reste vient tout seul, puisque q(y)=faux lorsque y est du type B (qui pourtant dérive de A).

  12. #12
    En attente de confirmation mail

    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2004
    Messages
    1 391
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 34
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Points : 3 311
    Points
    3 311
    Par défaut
    Ca dépend de quel type tu parles, avec un y de type dynamique B et de type statique A, ca reste vraie.

    Et comme dire : "on peut utiliser une fille partout où l'on peut utiliser une mère" revient à considérer que les objets sont de type statique celui de la mère (dans la méthode considérée). Ca revient à ce que le LSP reste valide si l'on se place dans une méthode (ie dans le corps de cette méthode l'énoncé formel est vérifié).

  13. #13
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Points : 13 017
    Points
    13 017
    Par défaut
    Salut,
    Citation Envoyé par Emmanuel Deloget Voir le message
    L'énoncé formel du LSP est (traduit par mes soins) : Soit q(x) une propriété prouvable à propos des objets x de type T. Alors q(y) doit être vrai pour les objets y de type S lorsque S est un sous-type de T. Voir http://blog.emmanueldeloget.com/inde...tion-de-liskov pour plus d'infos.

    Pour démontrer que le code présenté ci-dessus viole LSP, on pose juste q(x)=vrai si la fonction f() de x est publique pour tout x instance de A. Le reste vient tout seul, puisque q(y)=faux lorsque y est du type B (qui pourtant dérive de A).
    A mon avis, le problème vient quand on passe de l'énoncé 'formel' à son application concrète en C++. Comme le dit flob et comme je l'ai mentionné plus haut, une variable en C++ a deux types : un statique et un dynamique. Pour un pointeur et une référence ces deux types peuvent être distincts et respecter la propriété pour l'un (le statique) mais pas pour l'autre (le dynamique).
    J'avoue que sur le fond changer la visibilité d'une fonction fut-elle virtuelle me dérange. Mais de là à justifier par le LSP, c'est là où je butte. Je comprend ton argument (et il me semble que c'est ce qui sous tend ce que dit Koala), mais je m'interroge sur la pertinence d'une application trop conceptuel du LSP dégagée des contingences des types et des variables en C++. Pour être franc, mon avis n'est pas totalement formé (ni fermé) et je joue aussi un peu l'avocat du diable pour permettre justement l'échange d'arguments.

  14. #14
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 629
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 629
    Points : 30 692
    Points
    30 692
    Par défaut
    Citation Envoyé par Flob90 Voir le message
    Ca dépend de quel type tu parles, avec un y de type dynamique B et de type statique A, ca reste vraie.

    Et comme dire : "on peut utiliser une fille partout où l'on peut utiliser une mère" revient à considérer que les objets sont de type statique celui de la mère (dans la méthode considérée). Ca revient à ce que le LSP reste valide si l'on se place dans une méthode (ie dans le corps de cette méthode l'énoncé formel est vérifié).
    Sauf que tu fais, pour l'objet réellement manipulé, appel à une propriété qui n'est pas autorisée.

    Citation Envoyé par 3DArchi Voir le message
    Salut,
    A mon avis, le problème vient quand on passe de l'énoncé 'formel' à son application concrète en C++. Comme le dit flob et comme je l'ai mentionné plus haut, une variable en C++ a deux types : un statique et un dynamique. Pour un pointeur et une référence ces deux types peuvent être distincts et respecter la propriété pour l'un (le statique) mais pas pour l'autre (le dynamique).
    J'avoue que sur le fond changer la visibilité d'une fonction fut-elle virtuelle me dérange. Mais de là à justifier par le LSP, c'est là où je butte. Je comprend ton argument (et il me semble que c'est ce qui sous tend ce que dit Koala), mais je m'interroge sur la pertinence d'une application trop conceptuel du LSP dégagée des contingences des types et des variables en C++.
    Il ne faut pas oublié que le diagramme de classes n'est pas le seul diagramme que tu devrais créer lors de ta conception UML...

    Juste après, tu va créer... un diagramme d'objet, et des diagrammes de séquences, pour ne citer qu'eux, afin de vérifier tes cas d'utilisation.

    Et là, tu devrais te rendre compte que, lorsque tu passe un pointeur ou une référence sur un objet dérivé en le faisant passer pour un objet de la classe de base, tu autorise le recours à quelque chose... qui ne devrait pas l'être.
    Pour être franc, mon avis n'est pas totalement formé (ni fermé) et je joue aussi un peu l'avocat du diable pour permettre justement l'échange d'arguments.
    On ne t'en voudra pas pour cela... Tant que cela restera lors d'une discussion théorique (car, si tu me fais quelque chose de pareil dans mon équipe, tu passes par la fenêtre )

  15. #15
    En attente de confirmation mail

    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2004
    Messages
    1 391
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 34
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Points : 3 311
    Points
    3 311
    Par défaut
    Comment ca "qui ne devrait pas l'être" ? La visibilité est determiné par le type statique, donc la méthode est accesible, après que le C++ fonctionne comme ceci est peut-etre criticable, mais ca ne change pas le fait qu'il fonctionne ainsi et que par conséuqent, ca l'est.

    Mais sinon je suis d'accord que c'est pas du tout le genre de manip que je tenterais, vouloir changer la visibilité montre quand même qu'il y a quelque chose qui n'est pas clair, qui devrait êter revue.

  16. #16
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 629
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 629
    Points : 30 692
    Points
    30 692
    Par défaut
    En fait, la raison qui devrait te pousser à réfléchir en terme du type dynamique est relativement simple:

    Si tu fais dériver une classe d'une autre, c'est parce que tu sais que tu créera, tôt ou tard, une instance de cette classe dérivée... Autrement, tu ne l'aurais sans doute pas envisagée

    A partir du moment où tu envisage de créer une instance de cette classe dérivée, c'est parce que tu estimes que, tôt ou tard, tu voudra l'utiliser en tant que telle, et non comme une instance de la classe de base ou pire, comme une instance d'une autre classe dérivée.

    Si, alors que ton instance d'objet passe *temporairement* pour être du type de la classe de base, tu accepte qu'un comportement soit invoqué alors que l'invocation aurait été refusée si ton instance avait été considéré comme étant de son type réel, tu ouvre la porte à des modifications qui n'auraient normalement pas eu lieu d'être, et tu risque donc, lorsque tu considérera ton instance d'objet comme son type réel, de le retrouver dans un état incohérent par rapport au contexte dans lequel tu l'utilise.

    Je suis d'ailleurs content qu'Emmanuel confirme mon point de vue, car pour moi, il n'y aurait strictement aucun sens à permettre, sous prétexte que l'on travaille avec une instance objet passant pour être du type de base quelque chose qui aurait été refusé si l'instance d'objet avait été considérée comme étant... du type réel.

  17. #17
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 629
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 629
    Points : 30 692
    Points
    30 692
    Par défaut
    Citation Envoyé par Flob90 Voir le message
    Comment ca "qui ne devrait pas l'être" ? La visibilité est determiné par le type statique, donc la méthode est accesible, après que le C++ fonctionne comme ceci est peut-etre criticable, mais ca ne change pas le fait qu'il fonctionne ainsi et que par conséuqent, ca l'est.
    Le fait est que le type statique accepte quelque que le type dynamique n'accepte normalement pas, du moins en l'appelant depuis l'extérieur.

    Or, comme l'a fait valoir Emmanuel, si tu considère le fait qu'une fonction peut être appelée depuis l'extérieur depuis la classe de base comme vrai, cette assertion est fausse pour l'objet dérivé.

    Tu es donc clairement en violation du LSP, parce que cette propriété de la classe de base n'est pas valide pour la classe dérivée. Point barre...
    Mais sinon je suis d'accord que c'est pas du tout le genre de manip que je tenterais, vouloir changer la visibilité montre quand même qu'il y a quelque chose qui n'est pas clair, qui devrait êter revue.
    Et la meilleure raison (ou, à défaut la "moins mauvaise") réside, effectivement, dans le fait que l'on viole LSP.

    On pourrait poser la question de "mais pourquoi voudrais tu permettre quelque chose pour la classe de base et l'interdire pour la classe dériée cela n'a acun sens" (en toute honnêteté, je croyais d'ailleurs avoir posé cette question ), mais les raisons que l'on donnerait pour justifier ce non sens ne seraient pas différentes de celles que je viens de donner pour démontrer que l'on viole LSP

    Le pire de l'histoire, c'est que si j'avais directement présenté les choses sous cette forme (avec cette fameuse question, sans mentionner LSP), tout le monde aurait sans doute été d'accord, alors que cela revient exactement au même et que le raisonnement est, effectivement, basé sur LSP...

  18. #18
    En attente de confirmation mail

    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2004
    Messages
    1 391
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 34
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Points : 3 311
    Points
    3 311
    Par défaut
    Oui je suis d'accord que d'un point de vue formel, si tu appliques le principe directement alors il est violé. (c'est ce que j'ai dit dans ma première réponse )

    Cependant j'évoquais le cas plus haut où le LSP prend tout son sens : une fonction qui attend un objet de type mère, et si tu considères uniquement le corps de cette fonction et que tu y appliques le LSP sur l'objet passé en paramètre alors l'énoncé formel est vraie (du à la visibilité qui est statique et au fait que l'énoncé parle des propriété des objets pas des types).

    La question c'est de savoir si on doit considérer le LSP de manière global (ie juste du point de vue des classes), dans ce cas il est violé, ou juste dans le cas ou il est réelement utile (ie juste dans le corps des fonctions), dans ce cas il ne l'est pas. Quelqu'un l'a remarqué plus haut (je crois), c'est une différence de point de vue : selon les types ou selon les objets en eux-même.

    Personnelement je préfère considérer qu'il est violé aussi, mais ca ne m'empèche pas de quand même y voir une légère ambiguité.

    Le pire de l'histoire, c'est que si j'avais directement présenté les choses sous cette forme (avec cette fameuse question, sans mentionner LSP), tout le monde aurait sans doute été d'accord, alors que cela revient exactement au même et que le raisonnement est, effectivement, basé sur LSP...
    Je suis d'accord aussi, vouloir faire une tel chose semble étrange, et la réponse à la question que tu poses évidente. Mais ce n'est pas pour ca qu'une explication formel basé sur un principe est simple pour autant. (pour faire un parallèle : combien de théorème scientifique semble évident et sont pourtant de vraies casse-tête ?) Je ne suis pas certain que Mme Liskov ai pensé au cas ou un langage se servirait de deux type (statique/dynamique) pour determiner le comportement des méthodes (visibilité dans notre cas), quand elle a énoncé son principe

  19. #19
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 629
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 629
    Points : 30 692
    Points
    30 692
    Par défaut
    Citation Envoyé par Flob90 Voir le message
    Oui je suis d'accord que d'un point de vue formel, si tu appliques le principe directement alors il est violé. (c'est ce que j'ai dit dans ma première réponse )

    Cependant j'évoquais le cas plus haut où le LSP prend tout son sens : une fonction qui attend un objet de type mère, et si tu considères uniquement le corps de cette fonction et que tu y appliques le LSP sur l'objet passé en paramètre alors l'énoncé formel est vraie (du à la visibilité qui est statique et au fait que l'énoncé parle des propriété des objets pas des types).
    Là, il faut simplement considérer comment l'objet est passé en paramètre:

    Si c'est sous forme de valeur, tu as copie de l'objet (du type de base) ou conversion implicite avec création d'un objet de base (pour les types dérivés).

    A ce moment là, la virtualité et tout ce qu'elle implique n'a, de toutes manières, plus aucun sens, sans même compter sur le fait que, si l'héritage entre en jeu, nous partons quand même sur des objets ayant sémantique d'entité qui ont donc de grandes chances... d'être non copiables...

    Si c'est sous la forme de référence ou de pointeur, tu dois considérer le type réel de l'objet passé, c'est à dire le type dérivé, et il n'y a aucune raison pour que la fonction puisse invoquer un comportement défini comme protégé, même si elle est sensée travailler sur le type de base (dans lequel le comportement est défini comme publique).

    Dans le meilleur des cas, c'est un viol pur et simple des règles d'encapsulation, car cela revient à dire (comme j'en ai fait la remarque plus haut) "écoute, tu n'aurais normalement pas du appeler cette fonctions, mais comme tu a (été assez idiot pour perdre le type réel ) perdu le type réel de l'objet, et que tu as une bonne tête, je vais accepter d'effectuer l'action que tu me demande"
    La question c'est de savoir si on doit considérer le LSP de manière global (ie juste du point de vue des classes), dans ce cas il est violé, ou juste dans le cas ou il est réelement utile (ie juste dans le corps des fonctions), dans ce cas il ne l'est pas. Quelqu'un l'a remarqué plus haut (je crois), c'est une différence de point de vue : selon les types ou selon les objets en eux-même.
    A la base, tu dois le considérer de manière globale, car c'est lui qui te permettra de déterminer si une classe peut hériter valablement d'une autre ou non...

    LSP est... un principe de conception...

    Et, jusqu'à preuve du contraire, la conception a toujours lieu avant... la mise en oeuvre de ce qui a été conçu

    En effet, lorsque tu conçois tes différentes classes, il arrive que tu constate que "telle et telle classe" sont, définitivement fort proches, peut-être suffisamment proches pour que l'une dérive de l'autre.

    Tu va donc te poser la question "mais telle classe EST-ELLE-UN (telle autre classe) " tu va appliquer LSP et... passer toutes les propriétés de la classe de base afin de t'assurer qu'elle soit valide dans la classe dérivée.

    Et là, tu constatera que tu ne peux pas considérer une classe dérivée redéfinissant une fonction publique de la classe de base dans un niveau d'accessibilité moindre parce que... la propriété représentée par cette fonction ne sera plus valide (vrai d'un coté, faux de l'autre)

    Tu n'aura donc que peu de choix possibles:

    • Soit, tu crées une classe "ancêtre" sans cette propriété, et la classe présentant la fonction en visibilité publique et celle la prsentant en visibilité protégée deviennent soeurs / cousines
    • Soit tu définis la fonction dans la visibilité protégée pour la classe mère également
    • Soit tu abandonne l'idée de restreindre l'accès dans la classe dérivée
    • Soit enfin tu abandonne l'idée d'utiliser l'héritage public. (ce qui te forcera à envisager un certain nombre de possibilités )

    [EDIT]A l'extrême limite, j'aurais tout aussi bien pu m'écrier "STOP : problème de conception, revois la" lors de ma toute première intervention...

    On m'aurait alors poser la question de savoir pourquoi je réagis de la sorte, et j'aurais répondu "parce que, en vertu de LSP B n'est pas un A, vu que f est valide pour A et non pour B, et tout le monde aurait été d'accord, et la discussion se serait arrêtée là.
    [/EDIT]
    Personnelement je préfère considérer qu'il est violé aussi, mais ca ne m'empèche pas de quand même y voir une légère ambiguité.
    Comme je viens de le dire, il n'y a pas d'ambiguïté, vu que tu devrais normalement te poser la question bien avant d'envisager d'écrire la moindre fonction
    Je suis d'accord aussi, vouloir faire une tel chose semble étrange, et la réponse à la question que tu poses évidente.
    Tu me rassure...
    Mais ce n'est pas pour ca qu'une explication formel basé sur un principe est simple pour autant.
    Le fait est que toute la justification "tient" dans le principe en question, et que LSP est la base d'une conception OO "saine".
    (pour faire un parallèle : combien de théorème scientifique semble évident et sont pourtant de vraies casse-tête ?) Je ne suis pas certain que Mme Liskov ai pensé au cas ou un langage se servirait de deux type (statique/dynamique) pour determiner le comportement des méthodes (visibilité dans notre cas), quand elle a énoncé son principe
    Bien sur qu'il y a pensé, vu que, à la base, LSP est là pour assurer que le polymorphisme fonctionnera, et donc que tu demande l'adaptation du comportement de "quelque chose" "qui passe pour être" d'un type connu de manière à ce que ce soit le type réel de ce quelque chose qui soit pris en compte.

  20. #20
    En attente de confirmation mail

    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2004
    Messages
    1 391
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 34
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Points : 3 311
    Points
    3 311
    Par défaut
    <HS>Barbara Liskov, elle pas il :p</HS>

    Je considère par référence, et des méthodes virtuelles, sinon le LSP n'a aucun sens (méthodes non redéfinisables et pas de polymorsphisme => pas de LSP)

    Je vais prendre un exemple :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    class A { public: virtual void foo() {} };
    class B : public A { private: virtual void foo() {} };
    Je vais considérer la propriété P="peut recevoir le message foo", qui est donc simplement vérifiable à la compilation en tentant d'appeler foo sur un objet.

    Soit la fontion :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    void bar(A& a) { a.foo(); }
    L'écriture de ce code est envisageable, le passage par référence m'assure le polymorphisme, et la présence de foo dans l'espace publique de A couplé au LSP m'assure que ce code doit être fonctionnel pour tout objet d'un sous-type de A (B en particulier).

    Maintenant considérons un première portion de code de ce type :
    Ce code ne compile pas, on constate donc que la propriété P n'est pas vraie pour l'objet b1, donc le LSP est violé.
    Encuite un second passage :
    On constate que ce code compile, donc que la proriété P est vraie pour l'objet nommé 'a' dans le corps de la fonction, même quand celui-ci provient d'un B, on en conclu donc que le LSP est vraie dans le corps de la fonction.

    Tu noteras aussi que le fait de pouvoir passer (et que ca fonctionne) un objet de type B à la méthode bar qui attend une référence sur A valide le LSP si l'on considère l'autre définition (la non formel). Et que c'est quand même bien le but recherché (à ma connaisance) par le LSP que d'assurer que ce genre de chose soit possible, et que la définition formel est juste là pour avoir quelque chose de plus "propre".

    C'est donc ceci que je considère comme une ambiguité, le fait que le LSP soit violé du point de vue formel, mais correct là ou il est utile (dans le corps des fonctions qui profite du polymorphisme).

Discussions similaires

  1. Réponses: 2
    Dernier message: 02/10/2008, 16h37
  2. Fonction appelant une fonction virtuelle pure
    Par oodini dans le forum C++
    Réponses: 12
    Dernier message: 19/09/2008, 08h24
  3. Une fonction virtuelle ne peut pas retourner un template!
    Par coyotte507 dans le forum Langage
    Réponses: 10
    Dernier message: 08/02/2008, 20h39
  4. Problème de visibilité d'une fonction
    Par hello2lu dans le forum VBA Access
    Réponses: 8
    Dernier message: 03/07/2007, 15h20
  5. Réponses: 2
    Dernier message: 05/03/2006, 19h29

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo