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 :

[POO] Suivez-vous le principe de substitution de Liskov ?


Sujet :

C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Alp
    Alp est déconnecté
    Expert confirmé

    Avatar de Alp
    Homme Profil pro
    Inscrit en
    Juin 2005
    Messages
    8 575
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations forums :
    Inscription : Juin 2005
    Messages : 8 575
    Par défaut [POO] Suivez-vous le principe de substitution de Liskov ?
    Bonjour,

    Dans un monde où la POO est employée à toutes les sauces, dont certaines sont mauvaises, on voit des principes essentiels de programmation orientée objet bafoués.

    L'un d'entre eux est le Principe de Substitution de Liskov (Liskov Substitution Principle, LSP).

    En quoi consiste-t-il ?

    Introduit en 1987[1] par Barbara Liskov, le principe de substitution de Liskov est, formellement, le suivant :
    Si une propriété P est vraie pour une instance x d'un type T, alors cette propriété P doit rester vraie pour tout instance y d'un sous-type de T
    où dans notre cas les sous-types de T sont les classes qui dérivent de T.

    Moins formellement, il s'agit en fait que l'on puisse remplacer dans un code source toute instance de type Mere par une instance d'un type Fille qui dérive de Mere sans altérer en quoique ce soit la caractère correct du programme.

    La question est donc : suivez-vous ce principe dans vos hiérarchies de classes C++ ? Que pensez-vous de ce principe ? Lui trouvez-vous une utilité ? (j'espère )

    [1] Voir : http://citeseer.ist.psu.edu/cache/pa...ov94family.pdf

  2. #2
    Membre Expert
    Avatar de coyotte507
    Profil pro
    Inscrit en
    Octobre 2006
    Messages
    1 327
    Détails du profil
    Informations personnelles :
    Âge : 34
    Localisation : France

    Informations forums :
    Inscription : Octobre 2006
    Messages : 1 327
    Par défaut
    Avec la virtualité si on remplace les instances de type Mère par les instances de type Fille, en changeant rien d'autre, le programme aura très souvent un comportement différent.

  3. #3
    Alp
    Alp est déconnecté
    Expert confirmé

    Avatar de Alp
    Homme Profil pro
    Inscrit en
    Juin 2005
    Messages
    8 575
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations forums :
    Inscription : Juin 2005
    Messages : 8 575
    Par défaut
    Oui bien sûr, c'est le principe même de polymorphisme. Mais le programme se comportera correctement, c'est ça l'idée.

    Il s'agit de garder un programme correct en interchangeant des types dans une hiérarchie, cf le 1er post.

    Parfois, lorsqu'une classe B ne devrait pas hériter d'une classe A mais dont elle hérite quand même, on se retrouve avec un programme incohérent. C'est plutôt répandu comme violation du LSP.

  4. #4
    Membre éclairé Avatar de Kromagg
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2008
    Messages
    275
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : France, Ille et Vilaine (Bretagne)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Août 2008
    Messages : 275
    Par défaut
    Je ne connaissais pas ce principe, enfin je l'utilise souvent, mais je ne savais pas que c'était un principe de la POO et qu'il avait un petit nom
    Maintenant est-ce que le programme va se comporter correctement, je pense que tout dépend de ce que l'on fait dans les classes filles. Il pourrait très bien se comporter correctement, mais ne donnera pas le résultat voulu.

  5. #5
    Alp
    Alp est déconnecté
    Expert confirmé

    Avatar de Alp
    Homme Profil pro
    Inscrit en
    Juin 2005
    Messages
    8 575
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations forums :
    Inscription : Juin 2005
    Messages : 8 575
    Par défaut
    Citation Envoyé par Naoss Voir le message
    Je ne connaissais pas ce principe, enfin je l'utilise souvent, mais je ne savais pas que c'était un principe de la POO et qu'il avait un petit nom
    Maintenant est-ce que le programme va se comporter correctement, je pense que tout dépend de ce que l'on fait dans les classes filles. Il pourrait très bien se comporter correctement, mais ne donnera pas le résultat voulu.
    Oui oui, mais le but c'est qu'en remplaçant on n'aboutisse pas à une situation incohérente dans le programme.

  6. #6
    Membre éclairé Avatar de Kromagg
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2008
    Messages
    275
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : France, Ille et Vilaine (Bretagne)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Août 2008
    Messages : 275
    Par défaut
    On ne peut pas le garantir alors, tout dépend de la finalité de la classe fille, ce qu'elle va gérer. Je prend l'exemple d'un flux avec une classe mère comme ceci
    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
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
     
    class BaseStream
        {
        protected:
     
            /// Le flux est-il ouvert ?
            bool mOpen;
     
        public:
     
            /**
             * Constructeur par défaut.
             */
            BaseStream(void) {}
     
            /**
             * Destructeur.
             */
            virtual ~BaseStream(void) {}
     
            /**
             * Ouvre le stream.
             * @Param       _overWrite : mode d'ouverture du flux.
             * @Retour      True si l'ouverture à réussie, false sinon.
             */
            virtual bool open(bool _overWrite) = 0;
     
            /**
             * Lecture d'une portion des données du flux vers le buffer..
             * @Param               _buffer : buffer qui va contenir les données inscrite dans le flux.
             *                              _count : taille en bytes à lire dans le flux.
             * @Retour              Nombre de bytes lus.
             */
            virtual size_t read(char* _buffer, size_t _count) = 0;
     
            /**
             * Ecriture d'une portion des données du buffer vers le flux.
             * @Param               _buffer : buffer qui va contenir les données à inscrire dans le flux.
             *                              _count : taille en bytes à inscrire dans le flux.
             * @Retour              Nombre de bytes écris.
             */
            virtual size_t write(const char* _buffer, size_t _count) = 0;
     
            /**
             * Indique si la fin du flux est atteinte.
             * @Retour              True si c'est la fin, false sinon.
             */
            virtual bool isEndOfStream(void) const = 0;
     
            /**
             * Indique si le flux est ouvert.
             * @Retour      True s'il l'est, false sinon.
             */
            virtual bool isOpen(void) const = 0;
     
            /**
             * Ferme le flux.
             */
            virtual void close(void) = 0;
        };
    Maintenant 2 classes fille : DataStream et FileStream.
    Disons que FileStream est un flux charger de gérer un fichier (donc sur un disque physique) et DataStream est un flux contenant un ensemble de données gérer dans un buffer (en mémoire).
    Le but est de charger une ressource. Dans notre cas que la ressource soit présente sur le disque dur (fichier xml par exemple) ou en mémoire le résultat final sera le même, tout ce que l'on veut c'est charger une ressource, donc on peut dire ici que le programme devrait fonctionner correctement.

  7. #7
    Expert confirmé
    Avatar de Luc Hermitte
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2003
    Messages
    5 292
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Août 2003
    Messages : 5 292
    Par défaut
    On pourrait pinailler sur le fait que Barbara Liskov a introduit une définition, et que l'on a tiré un principe de cet article. Mais ce n'est pas important.
    Citation Envoyé par Alp Voir le message
    Moins formellement, il s'agit en fait que l'on puisse remplacer dans un code source toute instance de type Mere par une instance d'un type Fille qui dérive de Mere sans altérer en quoique ce soit la caractère correct du programme.

    La question est donc : suivez-vous ce principe dans vos hiérarchies de classes C++ ? Que pensez-vous de ce principe ? Lui trouvez-vous une utilité ? (j'espère )
    J'estime que ce principe est essentiel. Souvent on voit des critiques du C++ parce qu'il permet l'héritage multiple et que personne ne sait s'en servir.
    La vérité est que l'héritage fut vendu pour de mauvaises raisons (i.e. importer du code), et que beaucoup en sont restés là -- et ne savent pas s'en servir (alors imaginez l'héritage multiple). Avec une bonne compréhension et un respect du LSP, l'essentiel des critiques faites à l'encontre de l'héritage multiple s'évanouissent.

    Autrement, si dans une hiérarchie je n'ai pas de substituabilité, je passe en héritage privé (merci le C++) et basta: j'ai ainsi importé du code sans avoir fragilisé mon système en permettant des substitutions qui n'ont pas de sens ou qui sont instables.

    NB: Il est difficile de ne pas rapprocher le LSP de la programmation par contrats. Cf l'article de Robert C. Martin où il explique le LSP en s'appuyant sur des carrés et des rectangles. Cf aussi le fait qu'une liste triée, n'est pas une liste. Sujet déjà bien abordé par ici
    Blog|FAQ C++|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS|Bons livres sur le C++
    Les MP ne sont pas une hotline. Je ne réponds à aucune question technique par le biais de ce média. Et de toutes façons, ma BAL sur dvpz est pleine...

  8. #8
    Expert confirmé
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Décembre 2003
    Messages
    3 549
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

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

    Informations forums :
    Inscription : Décembre 2003
    Messages : 3 549
    Par défaut
    Il faut aussi faire attention aux invariants dans l'autre sens. Si tu as un Carré, sous-type d'un Rectangle, il ne faut pas qu'il soit possible de modifier l'objet par sa vue Rectangle de manière à ce que les invariants de Carré ne soient plus satisfaits.
    La solution évidente dans ce cas est d'avoir des objets immutables.

    J'utilise personnellement peu l'héritage et préfère les concepts. L'affinement de concepts est similaire à la dérivation.
    Mais lorsqu'on utilise les concepts, ce genre de propriétés est vraiment évident. D'autant plus qu'on ne manipule que des interfaces non-intrusives, et non pas des classes.

  9. #9
    Membre chevronné
    Avatar de Florian Goo
    Profil pro
    Inscrit en
    Septembre 2008
    Messages
    680
    Détails du profil
    Informations personnelles :
    Âge : 39
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Septembre 2008
    Messages : 680
    Par défaut
    Ce principe n'est ni plus ni moins que la raison d'être des interfaces.
    C'est pour cela qu'on dit souvent «Préférez la composition à l'héritage».
    Cours : Initiation à CMake
    Projet : Scalpel, bibliothèque d'analyse de code source C++ (développement en cours)
    Ce message a été tapé avec un clavier en disposition bépo.

  10. #10
    Expert confirmé
    Avatar de Luc Hermitte
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2003
    Messages
    5 292
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Août 2003
    Messages : 5 292
    Par défaut
    Citation Envoyé par Florian Goo Voir le message
    Ce principe n'est ni plus ni moins que la raison d'être des interfaces.
    C'est pour cela qu'on dit souvent «Préférez la composition à l'héritage».
    J'ai tendance à voir les interfaces comme des petites roulettes qui permettent d'apprendre à concevoir proprement sans s'en rendre compte. Et quand on est grand, on passe au pattern NVI

    Oui pour la deuxième affirmation si on restreint l'affirmation à l'héritage public. L'héritage privé convient parfaitement pour importer du code sans permettre d'être utilisable en place de (après tout nous sommes sur un forum C++ ; Java/C# en sont incapables, ruby a un autre mot clé pour, etc.). Le choix héritage privé / composition dépendant d'aspects liés au niveau de couplage acceptable, au type de fainéantise autorisée (après tout, c'est un héritage méconnu), ...
    Blog|FAQ C++|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS|Bons livres sur le C++
    Les MP ne sont pas une hotline. Je ne réponds à aucune question technique par le biais de ce média. Et de toutes façons, ma BAL sur dvpz est pleine...

  11. #11
    Membre chevronné
    Avatar de Florian Goo
    Profil pro
    Inscrit en
    Septembre 2008
    Messages
    680
    Détails du profil
    Informations personnelles :
    Âge : 39
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Septembre 2008
    Messages : 680
    Par défaut
    1) NVI, mmh ? Je vais aller voir ça

    2) Bien sûr , quand je parle d'héritage, je pense au sens POO strict. L'héritage privé est une autre façon d'écrire une composition (un prof, à l'époque, racontait même qu'en mémoire c'était strictement identique), mais ce n'est que de la syntaxe.
    Cours : Initiation à CMake
    Projet : Scalpel, bibliothèque d'analyse de code source C++ (développement en cours)
    Ce message a été tapé avec un clavier en disposition bépo.

  12. #12
    Modérateur
    Avatar de Obsidian
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Septembre 2007
    Messages
    7 452
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2007
    Messages : 7 452
    Par défaut
    Citation Envoyé par Alp Voir le message
    Si une propriété P est vraie pour une instance x d'un type T, alors cette propriété P doit rester vraie pour tout instance y d'un sous-type de T
    où dans notre cas les sous-types de T sont les classes qui dérivent de T.
    (Hello à tous)

    Je trouve ce principe trop vague, s'il est énoncé sous cette forme. Il faut au moins définir de façon rigoureuse la « propriété » d'une instance. Les différentes instances d'un même type doivent pouvoir être différentes les unes des autres, sinon ça ne servirait à rien de les instancier.

    Par exemple, un type T peut être celui d'un objet qui serait doté d'un membre de type int. Dans ce contexte, la propriété P d'une instance du type T serait, par exemple, d'avoir ce champ placé à la valeur 1000 (arbitrairement). Il est clair que les autres instances du type T ou de ses sous-types peuvent se voir attribuer des valeurs différentes au même membre.

    Je pense que ce principe est un fait le principe d'identité, de conservation de la nature, qui nous fait dire que « B est un A », quand la classe B dérive de A. En vertu de cela, je le respecte également.

    J'ajoute que faire un programme en C++ dont le comportement devient incohérent quand on substitue les classes-mères par leurs filles, ce n'est pas impossible mais il faut quand même le faire exprès. Au contraire d'autres langages, C++ ne privilégie pas par défaut les mécanismes tels que la virtualisation pouvant conduire à ce genre de situation si l'on n'y prend garde. À partir du moment où les membres sont conservés et que les méthodes appelées sont les mêmes, il ne reste pas grand chose pour causer des incohérences. Il faut pour cela que l'exploitation de la classe-fille introduise des données incohérentes aux yeux des méthodes de la classe-mère, et donc que le typage des membres soit volontairement trop permissif.

  13. #13
    Expert confirmé
    Avatar de Luc Hermitte
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2003
    Messages
    5 292
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Août 2003
    Messages : 5 292
    Par défaut
    Effectivement, la formulation initiale est subtilement différente.
    J'aime bien ce lien en français: http://www.crossbowlabs.com/dossiers/principesoo/lsp

    Citation Envoyé par Obsidian Voir le message
    J'ajoute que faire un programme en C++ dont le comportement devient incohérent quand on substitue les classes-mères par leurs filles, ce n'est pas impossible mais il faut quand même le faire exprès.
    Cela arrive très vite:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    struct SortedList : List {...};
    Il suffit d'avoir mal été formé/manquer de recul/être pressé/...
    Blog|FAQ C++|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS|Bons livres sur le C++
    Les MP ne sont pas une hotline. Je ne réponds à aucune question technique par le biais de ce média. Et de toutes façons, ma BAL sur dvpz est pleine...

  14. #14
    Expert confirmé

    Homme Profil pro
    Ingénieur systèmes et réseaux
    Inscrit en
    Février 2007
    Messages
    4 253
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Ingénieur systèmes et réseaux
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Février 2007
    Messages : 4 253
    Billets dans le blog
    3
    Par défaut
    Citation Envoyé par Obsidian Voir le message
    Par exemple, un type T peut être celui d'un objet qui serait doté d'un membre de type int. Dans ce contexte, la propriété P d'une instance du type T serait, par exemple, d'avoir ce champ placé à la valeur 1000 (arbitrairement). Il est clair que les autres instances du type T ou de ses sous-types peuvent se voir attribuer des valeurs différentes au même membre.
    Mais la tu confonds POO et implémentation (C++ pour le coup).
    En POO on oublie les classes, interfaces, public et privates, membres et autres fonctions... on parle de spécialisation ou généralisation ("l'héritage"), de composition, d'association, de dépendence, de liens, de "refining"....

    Si mes souvenirs sont bons (même si ils sont vieux, j'avoue ne pas avoir très assidu en cours de théorie des langages), le principe de Liskov est une formulation adaptée à la théorie des langages de la relation classiquement associée à l'héritage. En POO on dira "tout B est un A", avec Liskov on va dire, "B hérite de A si et seulement si, tout programme utilisant une instance de A peut utiliser une instance de B à la place sans qu'il ne devienne faux."
    Ce qui, soit dit au passage, est substantiellement différent de l'énoncé initial du post !

    C'est à dire, que les propriétés VISIBLES programmaticalement de l'objet ont un comportement sinon identique, en tout cas cohérent.... C'est à dire, grosso merdo, que toutes les fonctions accèssibles ont les mêmes pré et post conditions...

  15. #15
    Membre Expert
    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
    Par défaut
    C'est à dire, grosso merdo, que toutes les fonctions accessibles ont les mêmes pré et post conditions...
    En fait, pas tout à fait. C'est juste un problème d'héritage de contrat, et donc, les préconditions peuvent être étendues, et les post-conditions restreintes. Les invariants doivent être respectés.

    Carré, sous-type d'un Rectangle
    Ceci est une erreur de design (malheureusement enseignée...).

  16. #16
    Rédacteur

    Avatar de millie
    Profil pro
    Inscrit en
    Juin 2006
    Messages
    7 015
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2006
    Messages : 7 015
    Par défaut
    Citation Envoyé par white_tentacle Voir le message
    Ceci est une erreur de design (malheureusement enseignée...).
    Dans les vrais cours de COO (Conception OO), l'erreur là est souvent donné en premier exemple pour illustrer un non respect du principe de substitution de liskov.

    (mais faut il que des cours parle de ce principe ^^)

    Les cours dévient en général sur la méthode equals (opérateur =) d'ailleurs...

  17. #17
    Expert confirmé
    Avatar de Luc Hermitte
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2003
    Messages
    5 292
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Août 2003
    Messages : 5 292
    Par défaut
    Tu n'as pas utilisé le verbe auquel tu pensais réellement ; ou alors il manque un mot.

    Pour l'histoire du equals n'est-ce pas un autre problème propre aux langages incapables de définir des types utilisateurs à sémantique de valeur/qui font tout hériter d'un même objet racine ? Cela ne se pose typiquement pas en C++.
    Blog|FAQ C++|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS|Bons livres sur le C++
    Les MP ne sont pas une hotline. Je ne réponds à aucune question technique par le biais de ce média. Et de toutes façons, ma BAL sur dvpz est pleine...

Discussions similaires

  1. Réponses: 4
    Dernier message: 28/10/2018, 22h24
  2. Réponses: 2
    Dernier message: 28/11/2008, 10h30
  3. [POO] Voulez vous confirmer ma compréhension ?
    Par coussini dans le forum Autres
    Réponses: 5
    Dernier message: 22/05/2008, 15h34
  4. Réponses: 9
    Dernier message: 06/06/2007, 14h18
  5. [POO] [debutant] Comment pourriez vous traduire ceci?
    Par pierrot10 dans le forum Langage
    Réponses: 1
    Dernier message: 20/09/2006, 22h56

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