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

C++ Discussion :

Redéfinition de méthode et principe de Liskov


Sujet :

C++

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 612
    Points : 30 612
    Points
    30 612
    Par défaut
    Ce serait extrêmement vicieux. J'ai un A, j'appelle carre dessus, la postcondition sur le résultat me garantit qu'il est >= 0. La précondition sur A::f me dit que j'ai besoin que mon entier soit >= 0, ça tombe bien, c'est ce que carre me garantit !

    Si j'autorise B à restreindre les préconditions de A, mon programme va planter lamentablement, car je n'ai plus rien de tangible auquel me fier. J'ai perdu tout le bénéfice de mon contrat.
    Si, la cohérence du type réellement utilisé

    Mais je ne connais pas le type réellement utilisé. Le gros bénéfice de la PPC, c'est qu'elle me garantit que ce qui est valable pour un A, l'est pour tout B qui dérive de A.
    Mais à la limite, on s'en fout...

    Tu sais que tu vérifie le contrat, point barre.

    Ce que dit le contrat, à l'extrême limite, on s'en fout, on sais juste qu'il est là pour veiller à ce que l'objet reste cohérent.

    Si, parce que tu dérive de ton objet de base, les conditions du contrat doivent évoluer, je ne vois absolument pas pourquoi l'empêcher... de toutes manière, tu manipulera ton objet dérivé comme un objet de base: les comportements que tu invoquera à son sujet sont des comportements que tu sais que l'objet de base implémente.

    Je dirais même que tu te fout royalement de savoir si le comportement de l'objet dérivé est adapté ou non...

    La seule chose qui compte, c'est que si le comportement est adapté et que le contrat nécessite de "addenda" pour permettre au comportement d'avoir lieu, c'est que ce soit le contrat de l'objet dérivé qui prenne le pas sur le contrat de l'objet de base, point-barre, parce qu'un objet dérivé peut être considéré comme étant dans un état cohérent alors que l'objet de base, dans les mêmes circonstances, serait considéré comme étant dans un état incohérent, et vice versa...

    Ce qui importe, c'est que, lorsque l'on récupère l'objet réel (avec son type réel), l'objet soit bel et bien dans un état cohérent.

    S'il appartient au concepteur de l'objet de base de déterminer le contrat qui assure que l'objet est dans un état cohérent, et qu'il admet le fait que cet objet puisse être dérivé, c'est au concepteur de l'objet dérivé de déterminer le contrat qui assure qu'il est dans un état cohérent.

    Et l'utilisateur final n'a qu'à s'inquiéter de savoir s'il souhaite la vérification ou non du contrat, en sachant que, s'il la demande pour l'objet dérivé, il aura la certitude qu'il respecte les normes édictées pour cet objet particulier, ET que, s'il la demande pour l'objet de base (un objet réellement de base, et non un objet "qui se fait passer pour" un objet de base), ce seront les normes édictées pour cet objet de base qui seront prises en compte...

    Je ne vois absolument pas ce qui est choquant là dedans...

    Je sais que, bien que d'un point de vue purement géométrique, un carré est un rectangle "particulier", il ne faut pas permettre à carré d'hériter de rectangle parce que cela ne correspond à rien de parler de "longueur" et de "largeur" sur un carré.

    Par contre, un carré peut parfaitement hériter de losange (si on utilise comme donnée les quatre coins, et qu'il n'y a pas de données sur la taille des diagonales)
    Lorsque j'ai écrit main, je me suis basé sur ce que je connaissais. Le contrat de A::f. Je n'ai pas connaissance du contrat des classes dérivées. Je ne peux pas, leur ensemble est virtuellement infini. En supposant que je n'ai aucune règle sur le contrat dans les classes dérivées, je ne sais plus rien. J'ai un contrat, mais il ne me sert à rien, car je ne peux plus me baser dessus.
    Mais en tant qu'utilisateur de l'objet, tu n'en a de toutes façons pas besoin...

    Ce n'est pas à l'utilisateur de l'objet de décider du contrat, c'est à son concepteur, dés lors, si même tu viens à avoir 150 classes qui dérivent de ta classe de base, tu peux avoir entre 0 et 150 adaptation du contrat, c'est au concepteur de voir si elles sont nécessaires et opportunes

    En tant qu'utilisateur, tu sais que, quel que soit le type réel que tu aura choisi pour ta variable, si tu utilise le mode débug, tu aura une erreur si les conditions particulières du contrat ne sont pas respectées

    Honnêtement, je ne vois pas ce qui peut te chagriner là dedans.
    Ces règles sont là comme des garde-fous. Grâce à ces règles, je sais que je n'ai pas le droit d'écrire le B::f que j'ai écrit, et donc, que j'ai la garantie que ma fonction, qui fonctionne pour un A, fonctionnera pour tout B ou C qui dérive de A, sans que je doive modifier ma fonction appelante.
    Mais, pour autant que la vérification reste cohérente avec le type réel (que le contrat du type B soit cohérent avec le type B), tu garde exactement les même gardes fous... et ce n'est, je le redis encore, pas à l'utilisateur de s'en inquiéter, mais à celui qui crée les types en question...

    Cela reste tout à fait transparent pour l'utilisateur, et c'est bien ce qu'il faut.

    L'utilisateur sais qu'un contrat permet d'assurer la cohérence des données s'il passe en mode débug, et il sait que le concepteur du type aura pris ses précautions pour que, si le contrat nécessite une adaptation au type réel, ce soit cette adaptation qui sera prise en compte...
    Je crois qu'on a un problème de communication . J'ai l'impression que ce que tu défends, c'est que "on peut s'en sortir sans" et "c'est trop restrictif". Mon point de vue à moi, c'est que les garanties qu'on gagne, valent largement les restrictions qu'on s'impose.
    J'ai l'impression qu'il y a effectivement un problème de communication...

    Moi, ce que je défend, c'est le fait que chaque type doit être responsable des propres contrats qu'il doit appliquer, ce qui n'empêche absolument pas d'avoir un type dérivé qui utilise (tout ou partie) du "contrat type" de son type de base, en sachant que, si une adaptation de ce "contrat type" est nécessaire, cette adaptation doit être prise en compte lors de la vérification

    [EDIT]De manière beaucoup plus simplement exprimée: si tu adaptes un type, tu adaptes peut-être aussi les conditions dans lesquelles ce type est utilisé.

    Tu n'est pas obligé d'adapter les conditions d'utilisation, mais si tu le fait, il est normal que ce soit ces conditions adaptées qui servent de garde fou
    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

  2. #62
    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
    Je crois que notre problème de communication, c'est qu'on n'a pas la même définition de ce qu'est un contrat.

    Le contrat, tel que je l'entends (et tel qu'il est défini par B. Meyer), implique nécessairement deux parties, l'appelant et l'appelé. Les préconditions, c'est la partie qui concerne l'appelant. C'est son boulot de s'assurer que, lorsqu'il appelle l'appelé, il respecte les préconditions.
    Les postconditions (et les invariants), c'est la partie qui concerne l'appelé. C'est son boulot de s'assurer que, lorsqu'il rend la main, les postconditions sont respectées. C'est un engagement très fort, que prend chacune des parties, et sur laquelle l'autre peut compter.

    La cohérence de l'objet, c'est encore autre chose. Tu peux faire du contrat en fonctionnel pur, et tu peux très bien imaginer un contrat qui accepte que ton objet soit renvoyé dans un état incohérent.

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 612
    Points : 30 612
    Points
    30 612
    Par défaut
    Citation Envoyé par white_tentacle Voir le message
    Je crois que notre problème de communication, c'est qu'on n'a pas la même définition de ce qu'est un contrat.

    Le contrat, tel que je l'entends (et tel qu'il est défini par B. Meyer), implique nécessairement deux parties, l'appelant et l'appelé. Les préconditions, c'est la partie qui concerne l'appelant. C'est son boulot de s'assurer que, lorsqu'il appelle l'appelé, il respecte les préconditions.
    Les postconditions (et les invariants), c'est la partie qui concerne l'appelé. C'est son boulot de s'assurer que, lorsqu'il rend la main, les postconditions sont respectées. C'est un engagement très fort, que prend chacune des parties, et sur laquelle l'autre peut compter.

    La cohérence de l'objet, c'est encore autre chose. Tu peux faire du contrat en fonctionnel pur, et tu peux très bien imaginer un contrat qui accepte que ton objet soit renvoyé dans un état incohérent.
    J'admets volontiers ne pas connaitre la définition donnée par meyer (j'irai la chercher toute suite après avoir répondu) du contrat, mais quand bien même...

    Si l'appelant doit s'assurer qu'il peut appeler un comportement donné sur un objet, il est normal - à mon sens - qu'il s'en assure... sur l'objet réellement manipulé, et non sur l'objet sur lequel il *croit* (parce que c'est la forme sous laquelle on le lui passe) qu'il travaille

    Cela n'empêche absolument pas que l'invocation du comportement qui consiste à donner le feu vert (ca y est, je sais que je peux appeler le comportement), se fasse sur base d'un comportement "de vérification" connu du type de base

    De la même manière, si les posts conditions sont du ressort de l'appelé, il me semble normal qu'il respecte les conditions qui sont adapté au type réel, et non les conditions "pas forcément d'actualité" de son type de base

    Enfin, le C++ n'est pas un langage purement fonctionnel, et comme je n'ai une approche que trop succinte des langages purement fonctionnels, je ne prendrai pas le risque de parler de leur point de vue...

    Mais, je t'avouerai qu'en tant que programmeur OO, ma priorité première est la cohérence des objets que je manipule.

    Je suis peut-être en porte à faux avec les gurus sur ce point de vue, mais, de mon point de vue, un objet incohérent est un objet qui m'enverra dans le décors.

    Et donc, si je passe un contrat avec un objet, c'est bel et bien pour m'assurer que, quoi qu'il arrive, cet objet restera cohérent (soit parce que j'aurai effectivement eu l'occasion d'invoquer un comportement dessus, soit parce que l'objet m'aura signalé toute incohérence provoquée par ce comportement)

    Mais, de mon point de vue, le contrat doit donc s'appliquer sur l'objet réel, avec les conditions qui sont propres à cet objet réel, même s'il se fait passer pour autre chose
    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

  4. #64
    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
    Si l'appelant doit s'assurer qu'il peut appeler un comportement donné sur un objet, il est normal - à mon sens - qu'il s'en assure... sur l'objet réellement manipulé, et non sur l'objet sur lequel il *croit* (parce que c'est la forme sous laquelle on le lui passe) qu'il travaille
    Ce que tu décris, c'est une forme de programmation, où l'appelant demanderait à l'appelé s'il a le droit de l'appeler. Le problème de cette approche, c'est qu'elle impose de faire les vérifications au runtime. Ça me fait penser à une forme de programmation défensive, où tu confierais ta défense à l'appelant...

    L'avantage de la programmation par contrat, c'est que, du fait des contraintes supplémentaires que tu t'es imposées, tu sais quelles vérifications tu as besoin de faire, et quelles vérifications tu n'as pas besoin de faire.

    Notamment, tu sais que tu peux toujours écrire :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    int a = sqrt(carre(x));
    Mais pas :
    Autrement dit, grâce à la description de mes pré et post conditions, je sais que le premier va toujours marcher, sans avoir à rien vérifier, et je sais que le deuxième peut merder; et donc, que mon code est faux, et que je dois vérifier quelque chose. Ça me semble un bénéfice non négligeable !

    Une fois que tu as admis ce bénéfice sur l'approche fonctionnelle, essaie de transposer le mécanisme de contrat au monde de la POO, de l'héritage, en conservant ce bénéfice.

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 612
    Points : 30 612
    Points
    30 612
    Par défaut
    Hé bien, pourquoi ne pas accepter que le code suivant fournit effectivement les garanties nécessaire
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    class A
    {
        public:
            virtual void doSomething()
            {
                /* me jette si je n'ai pas le droit de faire ce qu'on attend
                 * du comportement
                 */
                assert(la vérif qui va bien )
                /* je fais ce qu'il faut */
                /* et je me fais jeter si la connerie a été faite */
                assert(la vérif finale)
            }
    };
    class B : public A
    {
        public:
            virtual void doSomething()
            {
                /* me jette si je n'ai pas le droit de faire ce qu'on attend
                 * du comportement
                 */
                assert(la vérif qui va bien, adaptée à mon B )
                /* je fais ce qu'il faut */
                /* et je me fais jeter si la connerie a été faite */
                assert(la vérif finale, adaptée à mon B)
            }
    };
    Si je l'appelle sous la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    int main()
    {
        A *a = new A();
        A *b = new B();
        a->doSomething();
        b->doSomething();
       delete a;
       delete b;
    }
    chacun appliquant les règles qui leur sont fixées, et, si, une autre personne a besoin de créer une fonction qui lui va bien, elle peut tout à fait créer sa fonction sur base d'une référence ou d'un pointeur sur A et appeler le comportement adéquat sous la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    void foo(A* a)
    {
        a->doSomething();
    }
    ou de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    void foo(A /*const*/ & a)
    {
        a.doSomething();
    }
    mais le contrat de chaque objet reste tout à fait valide par rapport à l'objet réellement utilisé...

    Honnetement, et j'admets être peut être en porte à faux avec meyer, je ne vois pas pourquoi les conditions qui s'appliquent à un A ne pourraient pas - si l'adaptation à l'objet le nécessite - s'adapter pour un B...

    Au moins, de mon coté, je garde la certitude que mes objets restent cohérents, et c'est la première chose que je demanderai toujours à mes comportements, parce qu'un objet incohérent m'enverra, tôt ou tard, dans le mur
    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

  6. #66
    Expert éminent sénior
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 369
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 369
    Points : 41 518
    Points
    41 518
    Par défaut
    Le problème, c'est que si un contrat devient dynamique, ce n'est plus un contrat.
    Ça équivaut à changer les règles du jeu en cours de partie, et bien que cela soit permis pour le Calvinball, ça n'est pas permis pour la programmation par contrat.
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

  7. #67
    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
    Hé bien, pourquoi ne pas accepter que le code suivant fournit effectivement les garanties nécessaire
    Il les fournit, mais, au runtime, avec un coût d'exécution associé (qui ne devient vraiment pas négligeable dans le cas, par exemple, d'un appel récursif).

    Le contrat, lui, te les fournit dès la conception !

    Ce que tu fais toi, c'est de la programmation défensive. Ça donne de bonnes garanties, mais avec un coût associé. La PPC te permet de t'affranchir totalement de ce coût, quand ton programme est correct.

    mais le contrat de chaque objet reste tout à fait valide par rapport à l'objet réellement utilisé...
    Comme dit, le contrat, il concerne deux "objets", l'appelant et l'appelé.

    Ah, une dernière chose. Dans ton exemple, doSomething n'a pas de précondition, puisqu'elle a un comportement défini quelque soient ses entrées (soit, ne rien faire, soit, exécuter le traitement).

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 612
    Points : 30 612
    Points
    30 612
    Par défaut
    Citation Envoyé par Médinoc Voir le message
    Le problème, c'est que si un contrat devient dynamique, ce n'est plus un contrat.
    Ça équivaut à changer les règles du jeu en cours de partie, et bien que cela soit permis pour le Calvinball, ça n'est pas permis pour la programmation par contrat.
    Ah, voilà enfin quelque une réponse qui vaudrait la peine de creuser

    Et malgré tout, ce n'est pas en cours de partie, que je propose de les adapter, mais bel et bien en cours de conception, lorsque je me rend compte qu'une règle (sans doute trop générale) empêche un comportement particulier de fonctionner comme il le devrait...

    Après tout, si j'en viens à décider de dériver un objet au départ d'un autre, c'est quand même - de prime abord - parce que mon étude des besoins ou ma conception me fait prendre conscience du fait que le type d'origine ne correspond pas forcément à un besoin particulier...

    Mais, qui dit besoin particulier dit - potentiellement -des restrictions particulières...

    Maintenant, indique moi où cette logique "théorique" se place dans un contexte de CalvinBall, et j'arrêterai de discuter
    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. #69
    Expert éminent sénior
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 369
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 369
    Points : 41 518
    Points
    41 518
    Par défaut
    Pour moi, ce que tu dis reflète plus une erreur dans la conception du contrat, et non un cas "normal".

    Mais c'est une autre remarque de white_tentacle qui me fait réfléchir:
    Ah, une dernière chose. Dans ton exemple, doSomething n'a pas de précondition, puisqu'elle a un comportement défini quelque soient ses entrées (soit, ne rien faire, soit, exécuter le traitement).
    Cela veut dire que le contrat ne protège que des comportements indéfinis.
    Si une fonction "marche" dans certains cas et retourne une valeur d'erreur dans d'autres, ça reste des comportements définis, qui peuvent donc faire partie du contrat.

    Simplement, ça donne un contrat où les postconditions ne sont pas constantes, mais conditionnelles.
    Exemple: "La fonction de calcul retourne ERROR_INVALID_PARAMETER si la valeur d'entrée n'est pas supporté" fait partie du contrat. Dans ce cas, le contrat ne dit pas "la valeur d'entrée doit être supportée", il dit seulement que le comportement n'est pas indéfini si une valeur non-supportée est passée en paramètre.
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

Discussions similaires

  1. redéfinition de méthode
    Par greg08 dans le forum Langage
    Réponses: 8
    Dernier message: 28/10/2008, 14h59
  2. Réponses: 8
    Dernier message: 01/07/2008, 23h15
  3. héritage et redéfinition de méthodes
    Par Braillane dans le forum Langage
    Réponses: 4
    Dernier message: 07/01/2007, 18h47
  4. redéfinition de méthode et invocation
    Par magedo dans le forum Langage
    Réponses: 2
    Dernier message: 05/01/2007, 19h00
  5. [JPanel][LayoutManager] redéfinition de méthodes
    Par _KB_ dans le forum AWT/Swing
    Réponses: 4
    Dernier message: 06/06/2006, 14h22

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