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. #21
    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
    La base de la confusion, c'est qu'il y a une différence de comportement entre instance et type.

    Le LSP s'applique à des objets (donc des instances), pas des types. Et vous pouvez retourner les choses dans tous les sens, mais si b est une instance de type B, et a une instance de type A, je n'arrive pas à trouver une propriété q(x) telle que q(a) soit vraie et pas q(b). Par contre, j'arrive tout à fait à trouver des propriétés q2(X) telles que q2(A) soit vraie et pas q2(B). Mais ce n'est pas ma compréhension du LSP, qui s'intéresse aux instances et pas aux types.

    Pour reprendre l'exemple que donnait Emmanuel :

    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).
    Il me dérange beaucoup. Une instance en C++ n'a pas de fonction publique, c'est son type qui a des fonctions publiques. La notion de visibilité n'intervient absolument pas au niveau des instances, c'est une notion qui n'existe qu'à la compilation, elle est même perdue dans l'exécutable.

    Sinon, je vois un cas d'utilisation où ça pourrait peut-être servir. Je fais une nouvelle version de mon interface, j'utilise un héritage public pour garder la compatibilité. Si je désire déprécier une fonction, je la passe protected pour que les développements nouveaux ne l'utilisent pas. Ca ne casse par contre pas le code qui utilise l'ancienne interface. Ca me semble tiré par les cheveux et il y a d'autres moyens (meilleurs) de gérer ce cas, mais je n'ai pas vraiment d'autre idée...

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par Flob90 Voir le message
    <HS>Barbara Liskov, elle pas il :p</HS>
    oupppsss... j'espère qu'elle ne m'en voudra pas pour cette erreur
    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)
    Nous sommes déjà d'accord sur ce point, c'est déjà pas si mal

    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.
    Mais tu dois vérifier LSP avant même d'écrire le code, au moment de la conception, quand tu décide de faire hériter B de A, et là...

    Tu te pose la question "B est-il un A "

    Tu réfléchira donc en terme de LSP:
    Citation Envoyé par Aplication de LSP
    je ne peux considérer que B est un A que si toute propriété valide pour A est également valide pour B...

    A dispose de la propriété d'accepter un appel externe à foo, c'est une propriété valide.

    Est-ce que cette propriété reste valide dans B
    Comme tu ne peux décemment pas estimer que ce soit le cas, tu ne peux pas accepter l'héritage public sous cette forme, et tu dois donc envisager une solution différente avant même d'écrire la première ligne de code (tu devrait barrer / supprimer la relation d'héritage publique directement dans ton diagramme de classe)

    Même si tu entre dans une logique que je qualifierais de "conception à la volée" (comprend: le fait de modifier la conception originale "pour les besoin de la cause", entre deux lignes de code), le résultat resterait le même:

    Tu en arriverais à une approche sous la forme de
    Citation Envoyé par moi à mon moi-même
    (Q) Mince, j'aurais besoin d'un type B qui a "presque tout" d'un A... Puis-je le faire hériter de A

    (R) Malheureusement, foo est publique dans A et protected dans B... les propriétés valides de A ne sont donc pas toutes valide pour B...

    Je ne peux donc pas envisager l'héritage public

    bar en est d'ailleurs la preuve: si je passe un B à bar, l'appel à foo serait jugé valide, alors que j'en ai explicitement interdit l'usage externe pour une instance de type B
    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é.
    Dans ce cas, il n'y a pas de polymoprhisme, donc, a priori pas besoin de s'inquiéter de LSP
    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.
    Non, on en conclu que C++ est un langage qui permet de se tirer une balle dans le pied et que c'en est un exemple supplémentaire.
    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).
    Mais on ne devrait pas avoir à considérer cette définition non formelle, vu que l'on est sensé avoir refusé l'idée même de l'héritage bien avant d'envisager de créer la fonction foo...
    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".
    Non, justement, LSP a pour but d'empêcher que ce soit possible...

    Je le répète: tu ne peux pas, sous prétexte que tu as été assez bête pour perdre le type réel (dynamique) d'une variable accepter un comportement qui n'aurait pas été accepté si tu avais disposé du type réel.
    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).
    Il n'y a pas d'ambiguïté possible, puisse que LSP doit être bloquant (dans le sens où il doit t'inciter à trouver une autre solution) avant même d'arriver à envisager la création des fonctions mettant le polymoprhisme en oeuvre.

    Citation Envoyé par white_tentacle Voir le message
    La base de la confusion, c'est qu'il y a une différence de comportement entre instance et type.

    Le LSP s'applique à des objets (donc des instances), pas des types. Et vous pouvez retourner les choses dans tous les sens, mais si b est une instance de type B, et a une instance de type A, je n'arrive pas à trouver une propriété q(x) telle que q(a) soit vraie et pas q(b). Par contre, j'arrive tout à fait à trouver des propriétés q2(X) telles que q2(A) soit vraie et pas q2(B). Mais ce n'est pas ma compréhension du LSP, qui s'intéresse aux instances et pas aux types.
    Le fait est qu'une instance d'objet reprsente "un objet du type indiqué", et que c'est le type de l'objet qui permet de définir les propriétés de celui-ci.

    Mais, encore une fois, LSP doit s'appliquer au moment de la conception et on doit donc se poser la question au moment de décider de faire hériter publiquement B de A et nous nous rendons alors compte que, si l'on crée un objet de type B, il y a une propriété qui n'est pas valide alors qu'elle l'est normalement pour le type de base, et ca, c'est inadmissible en terme de conception
    Il me dérange beaucoup. Une instance en C++ n'a pas de fonction publique, c'est son type qui a des fonctions publiques.
    C'est pour cela que l'on parle de propriété (sous entendu: "fournie par le type de l'instance en question")
    La notion de visibilité n'intervient absolument pas au niveau des instances, c'est une notion qui n'existe qu'à la compilation, elle est même perdue dans l'exécutable.
    La visibilité n'est aussi qu'une de conception, nous sommes bien d'accord...

    Mais cette notion est la base même de tout ce qui a trait à l'encapsulation, et donc, du droit d'accès que l'on a aux différents éléments qui constituent une classe (ou plutôt un type, pour rester suffisamment abstrait) concerné.

    Et LSP se base sur le principe d'encapsulation pour vérifier la validité des propriétés, vu qu'il a trait au propriétés publiques

    Nous ne devons donc pas accepter conceptuellement parlant que la visibilité d'une fonction impliquant le polymorphisme soit restreinte dans les types hérités.

    A l'extrême limite, bien qu'il faudrait m'apporter une justification correcte par rapport au cas bien précis qui implique ce genre de décision, voir diminuer les restrictions d'accès à une fonction virtuelle (la voir passer de private à protected / public ou la voir passer de protected à public dans le type dérivé).

    Les propriétés (publiques) valides pour la classe mère restent alors valides pour la classe dérivée, même si cette dernière expose une propriété supplémentaire.
    Sinon, je vois un cas d'utilisation où ça pourrait peut-être servir. Je fais une nouvelle version de mon interface, j'utilise un héritage public pour garder la compatibilité. Si je désire déprécier une fonction, je la passe protected pour que les développements nouveaux ne l'utilisent pas. Ca ne casse par contre pas le code qui utilise l'ancienne interface. Ca me semble tiré par les cheveux et il y a d'autres moyens (meilleurs) de gérer ce cas, mais je n'ai pas vraiment d'autre idée...
    On pourrait effectivement envisager les choses sous cet aspect, à condition de placer au minimum un avertissement à la compilation...

    Mais on s'éloigne alors très fort du problème de base
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  3. #23
    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
    Mais, encore une fois, LSP doit s'appliquer au moment de la conception et on doit donc se poser la question au moment de décider de faire hériter publiquement B de A et nous nous rendons alors compte que, si l'on crée un objet de type B, il y a une propriété qui n'est pas valide alors qu'elle l'est normalement pour le type de base, et ca, c'est inadmissible en terme de conception
    Essaie d'écrire cette "propriété qui n'est pas valide". Tu verras que c'est une propriété du type et non de l'instance (ce qui pour moi est d'autant plus différent que b est en fait à la fois du type A et du type B, grâce au polymorphisme).

    Je chipote, mais je suis comme 3DArchi. Autant j'aurais du mal à cautionner le fait de restreindre la visibilité dans une classe dérivée, autant je pense que ce n'est pas le LSP qui est en cause ici (le dernier argument que j'utiliserai pour essayer de te rallier à cette opinion : le LSP est avant tout utile pour se prémunir de plantages à l'exécution lorsqu'on utilise un objet de type B là où on utilise d'habitude un objet de type A. Ici, il n'y aura pas de problème. Le fait qu'on pourrait violer le LSP sans que ça pose problème, me pose problème ).

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par white_tentacle Voir le message
    Essaie d'écrire cette "propriété qui n'est pas valide". Tu verras que c'est une propriété du type et non de l'instance (ce qui pour moi est d'autant plus différent que b est en fait à la fois du type A et du type B, grâce au polymorphisme).
    Sauf qu'il ne devrait pas l'être...

    C'est au moment où tu décide des différentes propriétés de tes différents types que tu dois mettre LSP en oeuvre!!!

    Et, dans le cas présent, cette réflexion devrait te mener à... refuser de faire en sorte qu'un objet de type B soit un objet de type A.

    C'est comme si tu décidais qu'une voiture (type particulier) soit... un animal (type générique)!!!
    Je chipote, mais je suis comme 3DArchi. Autant j'aurais du mal à cautionner le fait de restreindre la visibilité dans une classe dérivée, autant je pense que ce n'est pas le LSP qui est en cause ici (le dernier argument que j'utiliserai pour essayer de te rallier à cette opinion : le LSP est avant tout utile pour se prémunir de plantages à l'exécution lorsqu'on utilise un objet de type B là où on utilise d'habitude un objet de type A. Ici, il n'y aura pas de problème. Le fait qu'on pourrait violer le LSP sans que ça pose problème, me pose problème ).
    Mais LSP intervient au moment de la CON-CEP-TION!!! et est un phénomène bloquant, dans le sens où, s'il n'est pas respecté, il faut envisager une solution différente.

    Autrement, LSP n'a strictement aucun sens, parce que, si tu attends l'implémentation de tes différentes classe, leur instanciation en terme d'objets et leur passage sous la forme de paramètres polymorphes pour vérifier LSP, il est évident qu'à partir du moment où tu as un héritage publique, tu peux faire passer n'importe quel objet comme étant... du type de la classe de base, même s'il n'y a (ou ne devrait en tout cas avoir) aucun lien cohérent entre les deux (exemple: une voiture héritant d'un animal et pouvant donc... passer pour un animal)...
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  5. #25
    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 : 33
    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
    Merci pour cette magnifique phrase (je suppose que les autres exemples sont le genre de chose comme des casts dangereux et autres comportements suicidaire permit par le langage )
    Non, on en conclu que C++ est un langage qui permet de se tirer une balle dans le pied et que c'en est un exemple supplémentaire.
    Je comprend maintenant. Le LSP est bien violé, et les exemples que je donne ne montre rien puisque le C++ permet d'appliquer le LSP (en tant que langage OO), mais ne l'impose pas, d'où la posibilité de compiler ce que j'ai donné comme exemple. J'ai pas l'habitude de m'abstraire totalement du langage quand je concoit (peut-etre car je ne connait que le C++ ?), d'ou le fait que j'anticipe trop tot sur les méchanisme du langage alors qu'ils ne devraient pas (du moins peu) intervenir dans la facon de faire une diagramme des classes par exemple.

    Le LSP s'applique à des objets (donc des instances), pas des types. Et vous pouvez retourner les choses dans tous les sens, mais si b est une instance de type B, et a une instance de type A, je n'arrive pas à trouver une propriété q(x) telle que q(a) soit vraie et pas q(b).
    La propriété P que j'ai énoncé plus haut ("peut recevoir le message foo"), est une propriété qui s'applique bien à des objets pas à des types (sinon ca serait : "a foo dans son interface").

    Citation: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".

    Non, justement, LSP a pour but d'empêcher que ce soit possible...
    Je sous-entendais que le LSP permet d'utiliser un objet de type fille l'a ou on attend un objet de type mère, ce bien ce qu'il permet pas ce qu'il empêche, désolé pour la confusion

    Dans ce cas, il n'y a pas de polymoprhisme, donc, a priori pas besoin de s'inquiéter de LSP
    Oui pardon, je voulais juste montré que la propriété n'était pas vérifié, mais la fin de la phrase n'a rien à faire là.

  6. #26
    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 Flob90 Voir le message
    La propriété P que j'ai énoncé plus haut ("peut recevoir le message foo"), est une propriété qui s'applique bien à des objets pas à des types (sinon ca serait : "a foo dans son interface").
    Et cette propriété est tout à fait vraie pour b...

    C'est au moment où tu décide des différentes propriétés de tes différents types que tu dois mettre LSP en oeuvre!!!
    Le fait est qu'ici, on a une différence entre instance et type qu'on n'a pas l'habitude d'avoir en programmation. C'est ça qui est perturbant, et qui conduit à nos différences d'interprétation. Mais je serais convaincu si vous réussisez à me trouver une propriété p(x) vraie pour p(a) et pas pour p(b). Moi je n'y arrive pas.

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Le fait est qu'aucun langage n'est capable de juger de l'intérêt de telle ou telle logique...

    Si tu décide de faire dériver ta classe voiture d'une classe animal, le langage n'a strictement aucun moyen de déterminer s'il est opportun ou non de le faire.

    Par contre, à partir du moment où tu as pris la décision de le faire, le langage acceptera que tu fasse passer un objet du type dérivé pour... un objet du type de base, et, dans ce cas, il acceptera que tu essaye d'invoquer depuis l'extérieur n'importe quelle fonction publique intervenant dans l'interface du type de base.

    N'oublie pas que la logique à suivre (que ce soit d'un point de vue algorithmique ou d'un point de vue de la conception d'un objet) ne dépend que... du développeur! Si tu utilises une mauvaise logique, aucun langage ne pourra te le faire savoir à partir du moment où... tu utilises la syntaxe correcte pour essayer de l'implémenter
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par white_tentacle Voir le message
    Et cette propriété est tout à fait vraie pour b...
    Absolument pas... a permet d'obtenir le message depuis... n'importe où, y compris depuis "l'extérieur" de l'objet, vu qu'il s'agit d'une fonction publique.

    b quand à lui ne permet d'obtenir le message que... depuis une de ses fonctions membres ou une des fonctions membres des types dérivés de B, vu qu'il s'agit d'une fonction protégée.

    La classe B impose donc une restriction que la classe A n'impose pas, et donc, on en revient toujours au même (j'ai vraiment l'impression de tourner en rond ): on ne peut pas admettre qu'une fonction soit appelée depuis l'extérieur lorsque l'objet passe pour être du type de base alors que l'accessibilité de la dite fonction a explicitement été restreinte pour le type réel.
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  9. #29
    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 : 33
    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
    Là je suis totalement d'accord avec Koala01.

    Et cette propriété est tout à fait vraie pour b...
    Non, ceci n'est pas vraie pour un b, c'est ce qu'explique Koala01. Conceptuellement un b ne peut recevoir le message foo (méthode privé), et (comme l'a expliqué Koala01), le fait que le C++ permette ce genre d'appel dans certain cas n'est qu'une des libertés offertes par le C++ qui ne doivent pas entrer en ligne de compte lors de la conception (ce que je n'arrivais pas à comprendre ). Et comme c'est à la conception qu'on cherche à vérifier le LSP, un b ne peut recevoir ce message.

    Fait un diagramme des classes sur papier, il apparait qu'un objet de type b ne peut recevoir le dit message (car la méthode n'est pas dans l'intrerface), et donc on a une proprété vraie pour un a et pas pour un b, conclusion LSP violé.

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par Flob90 Voir le message
    Là je suis totalement d'accord avec Koala01.


    Non, ceci n'est pas vraie pour un b, c'est ce qu'explique Koala01. Conceptuellement un b ne peut recevoir le message foo (méthode privé), et (comme l'a expliqué Koala01), le fait que le C++ permette ce genre d'appel dans certain cas n'est qu'une des libertés offertes par le C++ qui ne doivent pas entrer en ligne de compte lors de la conception (ce que je n'arrivais pas à comprendre ). Et comme c'est à la conception qu'on cherche à vérifier le LSP, un b ne peut recevoir ce message.

    Fait un diagramme des classes sur papier, il apparait qu'un objet de type b ne peut recevoir le dit message (car la méthode n'est pas dans l'intrerface), et donc on a une proprété vraie pour un a et pas pour un b, conclusion LSP violé.
    Ah, me voilà rassuré... tu as fini par comprendre

    Y a plus qu'à convaincre les autres
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  11. #31
    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 Flob90 Voir le message
    Fait un diagramme des classes sur papier, il apparait qu'un objet de type b ne peut recevoir le dit message (car la méthode n'est pas dans l'intrerface), et donc on a une proprété vraie pour un a et pas pour un b, conclusion LSP violé.
    Si je t'écoute, l'appel devrait échouer. Or, il fonctionne très bien. Ceci devrait t'amener à revoir ton raisonnement .

    b quand à lui ne permet d'obtenir le message que... depuis une de ses fonctions membres ou une des fonctions membres des types dérivés de B, vu qu'il s'agit d'une fonction protégée.
    Mais non, c'est faux ! L'appel fonctionne : b.A::f() marche très bien ! Je peux toujours appeler la fonction depuis n'importe où. Le fait que le langage m'impose une syntaxe pour le faire est totalement accessoire.

    Si je vous suis, sur un héritage multiple, il y'aurait forcément violation de LSP :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    struct A1{
    int f();
    };
    struct A2
    {
    int f();
    };
    struct Bp : public A1, public A2
    {
     
    }
    Il y'aurait rupture du LSP parce que pour appeler f depuis Bp, je suis obligé de préciser si c'est le f de A1 ou A2 ? Désolé mais là, je ne peux pas vous suivre.

    L'objectif du LSP, c'est simplement de garantir que si j'accède à mon b à travers l'interface A, j'ai un comportement conforme à ce que définit mon interface A (contrats et tout le tralala inclus). Et là, j'ai beau retourner dans tous les sens, je ne vois rien qui contredis cela.

    Je vais me répéter, mais ici :
    - une instance de type B reste substituable à une instance de type A.
    - le type B n'est pas substituable au type A (ce qui peut poser problème en terme de programmation générique).

    Donc soit vous considérez que le LSP s'applique aux types (et je crois que c'est la vision de Koala), soit vous considérez qu'il ne s'applique qu'aux instances (c'est ma vision), mais là il ne faut pas me parler de messages non recevables ou de je ne sais quoi, parce que ça n'est tout simplement pas vrai .

  12. #32
    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 : 33
    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 je t'écoute, l'appel devrait échouer. Or, il fonctionne très bien. Ceci devrait t'amener à revoir ton raisonnement .
    Là tu fais comme moi sur mon message précédent, tu utilises le langage pour vérifier quelque chose qui est conceptuel et non lié au langage.

    Mais non, c'est faux ! L'appel fonctionne : b.A::f() marche très bien ! Je peux toujours appeler la fonction depuis n'importe où. Le fait que le langage m'impose une syntaxe pour le faire est totalement accessoire.
    Et je sais pas si tu as testé, mais b.A::foo(); appel la méthode non redéfinit (ie tel qu'elle est dans A), c'est pas ce qui est recherché, c'est même le comportement opposé à ce qui est recherché avec la virtualité ! (comportement normal quand même)

    Non ton héritage multiple ne pose pas ce problème, les deux fonction sont bien présente et ce sont les bonnes qui sont appelées (car pas de virtualité, et si il y avait et que tu la redéfinisait il n'y aurait pas de problème non plus).

    - une instance de type B reste substituable à une instance de type A.
    D'après le langage oui, c'est le polymorphisme (et les règle dévalution de la visibilité), en phase de conception non.

  13. #33
    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 Flob90 Voir le message
    Là tu fais comme moi sur mon message précédent, tu utilises le langage pour vérifier quelque chose qui est conceptuel et non lié au langage.
    Non, je parle simplement de la différence type/instance. Tout tourne autour de ça.

    Et je sais pas si tu as testé, mais b.A::foo(); appel la méthode non redéfinit (ie tel qu'elle est dans A), c'est pas ce qui est recherché, c'est même le comportement opposé à ce qui est recherché avec la virtualité ! (comportement normal quand même)
    ((A*)&b)->f() si tu préfères .

    Non ton héritage multiple ne pose pas ce problème, les deux fonction sont bien présente et ce sont les bonnes qui sont appelées (car pas de virtualité, et si il y avait et que tu la redéfinisait il n'y aurait pas de problème non plus).
    Pourtant, il pose problème :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    template<class T>
    int f(T t)
    {
        return t.f();
    }
    Je peux écrire :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    A1 a1;
    f(a1);
    A2 a2;
    f(a2);
    mais pas :
    Vous avez dit substitution ? .

    D'après le langage oui, c'est le polymorphisme (et les règle dévalution de la visibilité), en phase de conception non.
    Je n'ai jamais parlé de phase de conception (pour moi, c'est juste hors-sujet), je parle uniquement d'instances et de types.

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par white_tentacle Voir le message
    Si je t'écoute, l'appel devrait échouer. Or, il fonctionne très bien. Ceci devrait t'amener à revoir ton raisonnement .



    Mais non, c'est faux ! L'appel fonctionne : b.A::f() marche très bien ! Je peux toujours appeler la fonction depuis n'importe où. Le fait que le langage m'impose une syntaxe pour le faire est totalement accessoire.
    C'est un problème de compilateur qui respecte simplement une décision qui n'a pas lieu d'être:

    Déjà, lorsque tu invoque b.a::f(), tu invoque une fonction qui, dans le meilleur des cas, ne fera qu'une partie de ce que tu veux et qui, dans le pire des cas, n'agira absolument pas comme tu serait en droit de l'attendre pour un objet de type B.

    Le fait que tu aies pris la décision de faire hériter B de A provoque cette autorisation, mais, à la base, l'héritage lui-même n'aurait jamais du être envisagé.

    Si je vous suis, sur un héritage multiple, il y'aurait forcément violation de LSP :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    struct A1{
    int f();
    };
    struct A2
    {
    int f();
    };
    struct Bp : public A1, public A2
    {
     
    }
    Il y'aurait rupture du LSP parce que pour appeler f depuis Bp, je suis obligé de préciser si c'est le f de A1 ou A2 ? Désolé mais là, je ne peux pas vous suivre.

    L'objectif du LSP, c'est simplement de garantir que si j'accède à mon b à travers l'interface A, j'ai un comportement conforme à ce que définit mon interface A (contrats et tout le tralala inclus). Et là, j'ai beau retourner dans tous les sens, je ne vois rien qui contredis cela.
    Cela n'a rien à voir avec l'histoire:

    Si tu peux effectivement estimer que tout objet de type Bp EST-UN objet de type A1 et que, dans le même temps, Bp EST-UN objet de type A2, LSP ne sera absolument pas violé.

    Mais pour que les deux relation EST-UN puisse être vérifiées, il faut, définitivement, que Bp garde l'interface publique de A1 et de A2.

    Accessoirement, il serait sans doute pas mal d'envisager de nommer les fonctions de manière différente pour éviter l'ambiguïté qui nous force à appeler explicitement A1::f() ou A2::f(), mais c'est un autre débat (celui de la sémantique donné au nom des fonctions )

    Je vais me répéter, mais ici :
    - une instance de type B reste substituable à une instance de type A.
    - le type B n'est pas substituable au type A (ce qui peut poser problème en terme de programmation générique).
    Parce que tu l'as décidé ainsi, et donc, le compilateur n'a d'autre choix que de... respecter tes décisions.

    Il n'a aucun moyen d'évaluer l'opportunité des décisions que tu as prises, or, c'est la décision même d'utiliser l'héritage public qui est remise en cause.

    Et la raison de cette remise en cause tient en trois lettres LSP.
    Donc soit vous considérez que le LSP s'applique aux types (et je crois que c'est la vision de Koala), soit vous considérez qu'il ne s'applique qu'aux instances (c'est ma vision), mais là il ne faut pas me parler de messages non recevables ou de je ne sais quoi, parce que ça n'est tout simplement pas vrai .
    La notion d'instance est indissociable de la notion de type, parce que, quand tu crées un objet ou une instance d'objet, il (elle) a... un type associé.

    Le type (la classe) de ton objet dit ce que tu peux et ce que tu ne peux pas faire avec ton objet, sur base des décisions prises par le développeur et représentées, entre autres, par la visibilité dans laquelle tu déclares, dans le cas présent, les fonctions qui composent le type

    Le compilateur n'a qu'une obligation: celle de respecter tes décisions. Il ne lui appartient pas d'évaluer l'opportunité de tes décisions, et il en serait d'ailleurs incapable (autrement, les ordinateurs se programmeraient tout seuls, et le temps de skynet ou de I Robot ne seraient plus loin).

    Si tu prend une décision inopportune, comme celle de faire hériter Voiture de Animal ou de faire hériter B de A, il n'est absolument pas dans les attributions du compilateur de te le faire savoir... Il n'en aurait de toutes façons pas les moyens.

    Par contre, il respectera les règles que cette décision inopportune impose.

    Dans le cas présent, cela impose (vu que l'objet de type B "passe pour être" un objet de type A) que... l'interface de l'objet expose publiquement la fonction foo.

    Or, et c'est justement là le noeud du problème, la décision de faire hériter B de A est inopportune justement parce que tu essaye de placer une restriction sur la capacité du développeur à faire appel à foo depuis un objet de type B, en la déclarant protégée: d'un point de vue purement conceptuel, tu accepte que l'on appelle la fonction depuis un objet (passant pour être) de type A, alors que tu refuse que l'on y fasse appel dans les même circonstances pour un objet de type B.

    Tu ne devra donc pas t'étonner si, à l'exécution, tu obtiens des résultats pour le moins aberrants.

    Le problème étant que le compilateur ne peut que... respecter tes décisions, aussi inopportunes puissent-elles être.

    De plus, j'attends toujours que l'on me dise quelle logique biscornue pourrait nous inciter à accepter qu'un objet (passant pour être) du type de base puisse exposer une fonction de manière publique alors qu'un objet du type dérivé puisse... refuser de réagir à l'appel de cette fonction dans des circonstances analogues.
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  15. #35
    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 : 33
    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
    Je n'ai jamais parlé de phase de conception (pour moi, c'est juste hors-sujet)
    Parler de LSP sans parler de conception est impossible.

    Sans parler du langage. Soit une classe A et une classe B fille de A. Dans A je définit une méthode foo, et dans B je fait de foo un détail d'implémentation (foo n'est donc plus dans l'interface). L'ensemble des messages recevable par un objet de type A est { foo } et celui recevable par un objet de type B est l'ensemble vide. Soit la propriété P "peut recevoir le message foo", on a que pour un objet de type A, P est vraie, et pour un objet de type B, P est faux, or B est un sous-type de A, on a donc une violation de l'énoncé formel du LSP => LSP violé. Donc tu ne doit pas prendre la décision d'hériter B de A.

    Après que la langage par ces méchanisme te permette d'introduire d'autre éléments dans ce raisonnement (prise en compte des deux typage pour determiner l'interface ou autre) ne doit pas être pris en compte. Comme le dit koala01:
    Si tu prend une décision inopportune, comme celle de faire hériter Voiture de Animal ou de faire hériter B de A, il n'est absolument pas dans les attributions du compilateur de te le faire savoir... Il n'en aurait de toutes façons pas les moyens.
    Le compilateur fait ce que tu lui dis de faire (si tu respectes la syntaxe et les règles), c'est à toi dans une phase préalable (ie la conception) de vérifier si tu respectes les principes OO, si tu décides de les violer alors oui ton code va compiler, c'est pas pour autant qu'il les respectera. (donc tu ne peus pas vérifier par des testes que les principes sont ou non totalement respecté)

    Et pour ton exemple d'appel de la bonne méthode, que ce ne soit qu'une syntaxe je veus bien, mais un cast c'est un peu plus que ca quand même.

  16. #36
    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 Flob90 Voir le message
    Sans parler du langage. Soit une classe A et une classe B fille de A. Dans A je définit une méthode foo, et dans B je fait de foo un détail d'implémentation (foo n'est donc plus dans l'interface). L'ensemble des messages recevable par un objet de type A est { foo } et celui recevable par un objet de type B est l'ensemble vide. Soit la propriété P "peut recevoir le message foo", on a que pour un objet de type A, P est vraie, et pour un objet de type B, P est faux, or B est un sous-type de A, on a donc une violation de l'énoncé formel du LSP => LSP violé. Donc tu ne doit pas prendre la décision d'hériter B de A.
    J'ai un peu l'impression de parler à un mur. Je viens de dire que l'interface d'un objet de type B contient aussi foo. Et j'en veux pour preuve que je peux l'appeler !

    Après que la langage par ces méchanisme te permette d'introduire d'autre éléments dans ce raisonnement (prise en compte des deux typage pour determiner l'interface ou autre) ne doit pas être pris en compte.
    Il l'est car c'est exactement la même chose. Soit tu considères que le fait de devoir changer l'écriture pour exploiter une fonctionnalité est une violation du LSP, auquel cas l'héritage multiple viole le LSP en cas de conflit de nom, soit tu considères que ce n'est pas le cas, et dans ce cas, la restriction de visibilité ne viole pas le LSP, car :

    - Tout le code écrit précédemment pour des instances de A (et donc, à l'exclusion des génériques, qui travaillent sur les types) fonctionnera avec des instances de B.

    Et c'est quand même ça qui est important dans le LSP.

    Tu ne devra donc pas t'étonner si, à l'exécution, tu obtiens des résultats pour le moins aberrants.
    Ce que j'essaie de vous faire comprendre, c'est justement que le changement de visibilité n'amène AUCUN comportement aberrant à l'exécution (contrairement à plein de trucs qu'on peut faire avec une redéfinition classique de fonction, vu que c++ n'a aucun mécanisme pour garantir le LSP).

    De plus, j'attends toujours que l'on me dise quelle logique biscornue pourrait nous inciter à accepter qu'un objet (passant pour être) du type de base puisse exposer une fonction de manière publique alors qu'un objet du type dérivé puisse... refuser de réagir à l'appel de cette fonction dans des circonstances analogues.
    A part l'exemple biscornu que j'ai donné, je n'en vois pas d'autre.

  17. #37
    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 : 33
    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
    J'ai un peu l'impression de parler à un mur. Je viens de dire que l'interface d'un objet de type B contient aussi foo. Et j'en veux pour preuve que je peux l'appeler !
    Oui, j'ai lu, et j'ai dit (et c'est ce que koala01 m'a expliqué plus tôt si j'ai bien compris) qu'il ne faut pas utiliser le langage pour vérifier, car le LSP est un principe général indépendant du langage, si tu fais un raisonnement abstraire (comme je l'ai réécrit) alors l'objet ne peut recevoir le message. De plus si je te donne un B classique (type statique et dynamique B), tu peus pas appeler le foo correspondant à un B sans caster (ou alors tu n'as pas encore dit comment faire ). Si le LSP prennait en compte les deux typage ensemble, ca pourrait être bon, mais ce n'est pas le cas (et ca supposerais que tout les langage OO avec un typage similaire à celui du C++, se comporte de la même manière, ce qui est peut-etre pas le cas).
    - Tout le code écrit précédemment pour des instances de A (et donc, à l'exclusion des génériques, qui travaillent sur les types) fonctionnera avec des instances de B.
    Oui, mais d'une part ceci n'est pas la définition foreml du LSP, et la formel n'est pas vérifié (cf mon post précédent), et d'autre part, c'est possible car le langage le permet, mais ce n'est pas logique, on ne devrait pas pourvoir utiliser des détail d'implémentation de B directement, le langage le permet c'est tout

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par white_tentacle Voir le message
    J'ai un peu l'impression de parler à un mur. Je viens de dire que l'interface d'un objet de type B contient aussi foo. Et j'en veux pour preuve que je peux l'appeler !
    Tu ne peux l'appeler que parce que, à la base, tu as pris une décision que tu n'aurais jamais du prendre: celle de faire hériter B de A alors que la sémantique EST-UN n'est pas respectée (car tu ne peux pas faire avec un B l'ensemble de ce que tu ferais avec un A, quitte à ce que les comportements soient adaptés)
    Il l'est car c'est exactement la même chose. Soit tu considères que le fait de devoir changer l'écriture pour exploiter une fonctionnalité est une violation du LSP, auquel cas l'héritage multiple viole le LSP en cas de conflit de nom,
    un conflit de noms est le symptôme typique d'un problème de conception, qui n'a rien à voir avec le respect de LSP, du moins en première analyse.

    Le fait que tu doive, pour lever l'ambiguïté, modifier la manière dont le code est écrit est... une preuve supplémentaire de ce problème de conception.

    Ce n'est donc pas l'héritage multiple -- pour autant que la relation EST-UN soit validée par LSP pour chaque classe de base héritée par la classe dérivée -- qui est en cause, mais... le choix qui a été fait au niveau du nom des différentes fonctions.
    soit tu considères que ce n'est pas le cas, et dans ce cas, la restriction de visibilité ne viole pas le LSP, car :

    - Tout le code écrit précédemment pour des instances de A (et donc, à l'exclusion des génériques, qui travaillent sur les types) fonctionnera avec des instances de B.
    Justement, ce n'est pas le cas:
    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
    class A
    {
        public:
            virtual void foo();
    };
    class B : public A
    {
        protected:
            virtual void foo();
    };
    int main()
    {
        A a;
        B b;
        a.foo(); //fonctionne...
        b.foo(); // devrait fonctionner, en vertu de LSP, mais refusé car foo est
                 // protégé pour B
    }
    Et c'est quand même ça qui est important dans le LSP.
    Ce qui est important dans LSP, c'est tu puisse obtenir un comportement cohérent avec le type réel de l'objet passé en paramètre lorsque tu transmet un type dérivé qui passe pour être du type de base.

    Lorsque tu apporte, dans le type dérivé, une restriction à l'utilisation d'un fonction déclarée publique dans la classe de base, tu obtiens un comportement incohérent, car le fait que l'objet de type dérivé passe pour être un objet du type de base fait "sauter" la restriction que tu as décidé de mettre en place.

    Le fait que le compilateur (et sans doute l'exécution) passe au travers de cela vient, encore une fois, parce qu'il n'est pas dans le pouvoir de ce dernier de vérifier si les décisions que tu as prises sont cohérentes...
    Ce que j'essaie de vous faire comprendre, c'est justement que le changement de visibilité n'amène AUCUN comportement aberrant à l'exécution (contrairement à plein de trucs qu'on peut faire avec une redéfinition classique de fonction, vu que c++ n'a aucun mécanisme pour garantir le LSP).
    Aucun langage n'a de mécanisme pour garantir LSP, vu que c'est un principe de conception...

    Tout ce que le langage peut faire, c'est d'accepter ou de refuser quelque chose sur base de règles que tu as toi même déterminées.

    Si tu détermines qu'un type donné hérite (de manière pubique) d'un autre, tu détermines implicitement certaines règles, qui font que tu peux faire passer un objet du type dérivé pour un objet du type de base.

    Le compilateur ne pourra donc pas te refuser d'agir de la sorte.

    Le problème, c'est que tout objet "passant pour être du type de base" existe par ailleurs (comprend: en dehors de la fonction dans laquelle l'objet passe pour être du type de base) et est d'un type différent (du type dérivé), et qu'il faut veiller à ce que tous les comportements restent cohérent avec ce type réel.

    Or, si tu refuse que l'on puisse appeler une fonction depuis l'extérieur d'un objet, il est totalement incohérent de permettre l'appel à cette fonction sous prétexte que l'on a "perdu le type réel" au moment de passer l'objet en paramètre.
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  19. #39
    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
    Tu ne peux l'appeler que parce que, à la base, tu as pris une décision que tu n'aurais jamais du prendre: celle de faire hériter B de A alors que la sémantique EST-UN n'est pas respectée (car tu ne peux pas faire avec un B l'ensemble de ce que tu ferais avec un A, quitte à ce que les comportements soient adaptés)
    Sisi, je peux toujours et je crois qu'on est pas d'accord sur la signification du verbe pouvoir .

    un conflit de noms est le symptôme typique d'un problème de conception, qui n'a rien à voir avec le respect de LSP, du moins en première analyse.

    Le fait que tu doive, pour lever l'ambiguïté, modifier la manière dont le code est écrit est... une preuve supplémentaire de ce problème de conception.

    Ce n'est donc pas l'héritage multiple -- pour autant que la relation EST-UN soit validée par LSP pour chaque classe de base héritée par la classe dérivée -- qui est en cause, mais... le choix qui a été fait au niveau du nom des différentes fonctions.
    Pourtant, ce choix aboutit à la même conséquence que le changement de visibilité : la fonction f est appelable au travers de l'interface de base, mais plus sur le type réel de l'objet (pour une raison différente, mais la conséquence est la même).

    Justement, ce n'est pas le cas:
    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
    class A
    {
        public:
            virtual void foo();
    };
    class B : public A
    {
        protected:
            virtual void foo();
    };
    int main()
    {
        A a;
        B b;
        a.foo(); //fonctionne...
        b.foo(); // devrait fonctionner, en vertu de LSP, mais refusé car foo est
                 // protégé pour B
    }
    Pourquoi ce code devrait-il fonctionner en vertu du LSP ? Où est la substitution ? Nulle part dans ce code on a substitué un B à un A. On a fait quelque chose sur un A, puis on essaie de faire quelque chose d'invalide avec un B. Mais nulle part il y a substitution, et certainement pas au niveau des objets.

    Ce qui est important dans LSP, c'est tu puisse obtenir un comportement cohérent avec le type réel de l'objet passé en paramètre lorsque tu transmet un type dérivé qui passe pour être du type de base.
    Enlève moi tous ces types que je ne saurais voir, je dis depuis le début que le LSP s'applique aux instances. Je reformule, donc :

    Citation Envoyé par corrige
    Ce qui est important dans LSP, c'est tu puisse obtenir un comportement cohérent avec l'objet passé en paramètre lorsque tu transmet un objet d'un type dérivé du type de base.
    Et là, fondamentalement, je ne vois plus où est le problème à restreindre la visibilité .

    Mais je te met au défi d'écrire du code qui fonctionne avec une instance de A, qu'on souhaite réutiliser tel quel en lui substituant une instance de B à la place, et où ça ne marche pas. Et je parle bien d'instance, pas de type.
    Le fait que le compilateur (et sans doute l'exécution) passe au travers de cela vient, encore une fois, parce qu'il n'est pas dans le pouvoir de ce dernier de vérifier si les décisions que tu as prises sont cohérentes...
    Aucun langage n'a de mécanisme pour garantir LSP, vu que c'est un principe de conception...
    Eiffel offre des mécanismes de ce type, au moyen de contrats (même si eiffel offre d'autres mécanismes qui ne respectent pas le LSP ). Mais je ne parle pas d'écrire du code qui ne vérifie pas le LSP (ce qui est très facile), je parle spécifiquement d'écrire du code qui aurait un comportement incohérent, en utilisant la fonctionnalité dont on est en désaccord pour savoir si elle respecte le LSP, et que l'incohérence soit due à une substitution d'objet. Ce qui me semble impossible, mais j'ai pu louper quelque chose.

    Tout ce que le langage peut faire, c'est d'accepter ou de refuser quelque chose sur base de règles que tu as toi même déterminées.
    Et celles que le langage accepte. Ce qui est loin d'être négligeable dans certains langages.

    Le problème, c'est que tout objet "passant pour être du type de base" existe par ailleurs (comprend: en dehors de la fonction dans laquelle l'objet passe pour être du type de base) et est d'un type différent (du type dérivé), et qu'il faut veiller à ce que tous les comportements restent cohérent avec ce type réel.

    Or, si tu refuse que l'on puisse appeler une fonction depuis l'extérieur d'un objet, il est totalement incohérent de permettre l'appel à cette fonction sous prétexte que l'on a "perdu le type réel" au moment de passer l'objet en paramètre.
    C'est tout le fond de notre désaccord. Pour moi, ce n'est pas nécessairement incohérent, et c'est rau contraire endu nécessaire par... le LSP .

  20. #40
    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 : 33
    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
    Eiffel n'a pas le même mécanisme de typage, (de mémoire, "ce qui se comporte comme un canard est un canard" est une phrase qui résume l'idée général), je crois que c'est une approche différente de celle de Mme Liskov.

    Le code que propose koala doit compiler en vertue du LSP (je répète pas l'énoncé ...), une instance de type A peut recevoir le message foo, donc une instance de type B aussi (car B sous-type de A), or le code ne compile pas. (l'énoncé formel du LSP ne parle pas de substitution ...)

    Citation:Ce qui est important dans LSP, c'est tu puisse obtenir un comportement cohérent avec le type réel de l'objet passé en paramètre lorsque tu transmet un type dérivé qui passe pour être du type de base.

    Enlève moi tous ces types que je ne saurais voir, je dis depuis le début que le LSP s'applique aux instances. Je reformule, donc :
    AMA, koala a juste oublie décrire "objet de" : "objet de type dérivé", sinon la phrase n'a aucun sens, il parle donc bien d'instance

    Quand on passe l'objet par référence (ou pointeur), on perd une partie du type (le type statique devient le type mère), et grace à ces mécanismes le C++ accepte d'appeler la méthode, mais c'est entièrement du au fait que tu perds une partie de l'information :
    é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
    Je cite 3DArchi:
    A mon avis, le problème vient quand on passe de l'énoncé 'formel' à son application concrète en C++. [...] une variable en C++ a deux types : un statique et un dynamique.
    Et évidement si tu effectues une approche basé sur le C++ tu te retrouves avec l'ambiguité que je constatais au début, mais en réalité elle n'existe pas car la vérification du LSP doit se passer avant de considérer le langage utilisé, et par conséquent il n'y a plus à considérer deux types distinct, ni une perte d'information. En faisant abstraction du langage la violation du LSP apparait plus simplement

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