Forum des développeurs  

Le forum de référence en programmation et développement. Articles, cours et tutoriels du débutant au chef de projet et DBA confirmé.
Précédent   Forum des développeurs > C et C++ > C++

C++ Forum d'entraide technique sur le langage C++. Avant de poster -> F.A.Q C++

Réponse
 
Outils de la discussion
Vieux 08/11/2008, 16h03   #1 (permalink)
Alp
Responsable C++
 
Avatar de Alp
 
Date d'inscription: juin 2005
Messages: 6 307
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
__________________
Mon blog (en) - Mes articles et critiques de livres - Brute
C++ - Qt - Algo - Intelligence Artificielle
Pour participer, contactez-moi


A la recherche d'un emploi à temps partiel si possible (Software, Web, ...)

You see things as they are and ask, 'Why?' I dream things as they never were and ask, 'Why not?' (George Bernard Shaw).
Alp est déconnecté   Envoyer un message privé Réponse avec citation
Vieux 08/11/2008, 16h17   #2 (permalink)
Membre Expert
 
Avatar de coyotte507
 
Date d'inscription: octobre 2006
Messages: 1 072
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.
coyotte507 est déconnecté   Envoyer un message privé Réponse avec citation
Vieux 08/11/2008, 16h20   #3 (permalink)
Alp
Responsable C++
 
Avatar de Alp
 
Date d'inscription: juin 2005
Messages: 6 307
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.
__________________
Mon blog (en) - Mes articles et critiques de livres - Brute
C++ - Qt - Algo - Intelligence Artificielle
Pour participer, contactez-moi


A la recherche d'un emploi à temps partiel si possible (Software, Web, ...)

You see things as they are and ask, 'Why?' I dream things as they never were and ask, 'Why not?' (George Bernard Shaw).
Alp est déconnecté   Envoyer un message privé Réponse avec citation
Vieux 08/11/2008, 16h26   #4 (permalink)
Membre à l'essai
 
Date d'inscription: août 2008
Localisation: La semaine sur Brest, le week-end sur Caen :)
Âge: 22
Messages: 42
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.
Naoss est déconnecté   Envoyer un message privé Réponse avec citation
Vieux 08/11/2008, 16h33   #5 (permalink)
Alp
Responsable C++
 
Avatar de Alp
 
Date d'inscription: juin 2005
Messages: 6 307
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.
__________________
Mon blog (en) - Mes articles et critiques de livres - Brute
C++ - Qt - Algo - Intelligence Artificielle
Pour participer, contactez-moi


A la recherche d'un emploi à temps partiel si possible (Software, Web, ...)

You see things as they are and ask, 'Why?' I dream things as they never were and ask, 'Why not?' (George Bernard Shaw).
Alp est déconnecté   Envoyer un message privé Réponse avec citation
Vieux 08/11/2008, 16h49   #6 (permalink)
Membre à l'essai
 
Date d'inscription: août 2008
Localisation: La semaine sur Brest, le week-end sur Caen :)
Âge: 22
Messages: 42
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 :
 
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.
Naoss est déconnecté   Envoyer un message privé Réponse avec citation
Vieux 08/11/2008, 17h06   #7 (permalink)
Membre Expert
 
Avatar de coyotte507
 
Date d'inscription: octobre 2006
Messages: 1 072
Par défaut

Je le bafoue : j'exige des paramètres dans le constructeur ou une initialisation différente pour certaines classes dérivées -- éventuellement transtypées ensuite en classes de base.

Mais je pense pas que je doive changer mon code
coyotte507 est déconnecté   Envoyer un message privé Réponse avec citation
Vieux 08/11/2008, 18h22   #8 (permalink)
Responsable C++
 
Avatar de koala01
 
Date d'inscription: octobre 2004
Localisation: Musson, au milieu de nulle part en Belgique
Âge: 36
Messages: 3 160
Envoyer un message via MSN à koala01
Par défaut

Salut,

J'essaie - généralement - de le respecter, et principalement lorsque j'essaye d'expliquer ce qu'est l'héritage.

C'est la raison pour laquelle j'insiste sur la valeur "sémantique" des termes utilisés pour représenter deux classes pour lesquelles l'héritage est envisagé, en essayant d'inciter la personne à se poser la question de savoir "s'il est sémantiquement correct de dire que la classe B est réellement une 'amélioration' de la classe A, ou s'il s'agit d'un objet se contentant d'avoir les mêmes caractéristiques".

Sémantiquement, parlant, il est effectivement correct de dire que "l'homme est un (animal) mammifère", alors que, effectivement, bien que les caractéristiques soient identique, il est impossible de prétendre qu'une pile (qui utilise, rappelons le, le principe LIFO) est une file (qui utilise, elle, le principe FIFO), ou vice-versa.

Ceci n'empêchant nullement de trouver une "point sémantiquement commun" dans une autre classe que nous pourrions nommer "structure dynamique"

De la même manière, et malgré les écueils qui risquent quasi immanquablement de se présenter, je n'ai aucune difficulté à admettre l'héritage multiple: il est sémantiquement correct de dire q'une turbo-génératrice est une génératrice, tout comme il est sémantiquement correct de dire que c'est un turbine
__________________
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
Vous avez obtenu votre réponse pensez au bouton en bas de page

A méditer: La solution la plus simple est toujours la moins compliquée
koala01 est déconnecté   Envoyer un message privé Réponse avec citation
Vieux 08/11/2008, 20h10   #9 (permalink)
Expert Confirmé Sénior

 
Avatar de Luc Hermitte
 
Date d'inscription: août 2003
Localisation: Toulouse
Messages: 3 449
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
__________________
FAQ C++|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS
Les MP ne sont pas une hotline, ne vous attendez pas à ce que l'on réponde à vos questions techniques par MP.

Dernière modification par Luc Hermitte ; 08/11/2008 à 23h19
Luc Hermitte est déconnecté   Envoyer un message privé Réponse avec citation
Vieux 08/11/2008, 23h16   #10 (permalink)
Provisoirement toléré(e)
 
Date d'inscription: décembre 2003
Messages: 3 092
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.
__________________
Boost ftw
loufoque est déconnecté   Envoyer un message privé Réponse avec citation
Vieux 09/11/2008, 01h03   #11 (permalink)
Membre Confirmé
 
Avatar de Florian Goo
 
Date d'inscription: septembre 2008
Messages: 263
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».
__________________
Socoa, Bibliothèque d'analyse de code source C++ (développement en cours)
Florian Goo est déconnecté   Envoyer un message privé Réponse avec citation
Vieux 09/11/2008, 04h10   #12 (permalink)
Expert Confirmé Sénior

 
Avatar de Luc Hermitte
 
Date d'inscription: août 2003
Localisation: Toulouse
Messages: 3 449
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), ...
__________________
FAQ C++|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS
Les MP ne sont pas une hotline, ne vous attendez pas à ce que l'on réponde à vos questions techniques par MP.
Luc Hermitte est déconnecté   Envoyer un message privé Réponse avec citation
Vieux 09/11/2008, 05h38   #13 (permalink)
Membre Confirmé
 
Avatar de Florian Goo
 
Date d'inscription: septembre 2008
Messages: 263
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.
__________________
Socoa, Bibliothèque d'analyse de code source C++ (développement en cours)
Florian Goo est déconnecté   Envoyer un message privé Réponse avec citation
Vieux 09/11/2008, 09h16   #14 (permalink)
Rédacteur
 
Avatar de Davidbrcz
 
Date d'inscription: juin 2006
Localisation: Systeme Solaire,La Terre,Europe,France,IDF, Paris
Messages: 1 308
Envoyer un message via MSN à Davidbrcz
Par défaut

1) NVI= Non virtual interface. En rapide, ne pas rendre virtuelles les fonctions membres publiques qui devrait l'être mais préférer qu'elles appelent des fonctions membres protected qui elles le serront
__________________
Partager grâce à l'open source et aux logiciels libres.

"Never use brute force in fighting an exponential." (Andrei Alexandrescu)

Conseils perso en vrac sur le C++ Une très bonne doc sur la STL (en) Why linux is better (fr)

Dernière modification par Davidbrcz ; 09/11/2008 à 13h18
Davidbrcz est déconnecté   Envoyer un message privé Réponse avec citation
Vieux 09/11/2008, 10h50   #15 (permalink)
Expert Confirmé Sénior

 
Date d'inscription: novembre 2005
Messages: 3 881
Par défaut

Citation:
Envoyé par Alp Voir le message
La question est donc : suivez-vous ce principe dans vos
hiérarchies de classes C++ ?
Durant la conception, oui. A noter que j'ai tendance à séparer mentalement
la conception de sa réalisation et que si la notion abstraite de hiérarchie
de type va s'implémenter avec de la hiérarchie de classes et alors
respecter ce principe, je ne m'interdit pas d'utiliser le mécanisme C++
d'héritage pour autre chose -- auquel cas le principe pourrait ne pas être
respecté (mais je n'ai pas de cas sous la main).


Citation:
Envoyé par loufoque Voir le message
J'utilise personnellement peu l'héritage et préfère
les concepts.
Je ne suis pas sûr de te comprendre.

D'une part en ce qui concerne le sujet de la discussion, quand tu ajoutes:

Citation:
L'affinement de concepts est similaire à la dérivation.
Tu sembles conscient qu'il y a bien derrière une même notion et donc que le
principe de substitution devrait s'appliquer exactement de la même manière.


Si tu écris cela d'une manière plus générale, je ne sais pas de quoi tu
parles précisément: il ne me semble pas que j'ai à faire un choix entre les
deux.


Est-ce que tu veux dire que tu préfères le polymorphisme paramétrique au
polymorphisme d'inclusion? Mais les concepts sont justement de mon point
de vue un mécanisme de polymorphisme d'inclusion (d'où l'applicabilité du
LSP).

Est-ce que tu veux dire que tu préfères l'utilisation de mécanismes
statiques aux mécanismes dynamiques? Quand j'ai le choix aussi, mais les
mécanismes dynamiques sont plus généraux et on n'a pas toujours le choix.


Citation:
Envoyé par Florian Goo Voir le message
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.
Et comment est-ce que tu supplantes un membre virtuel avec de la
composition? (Il y a au moins une autre différence, purement technique,
qui fait qu'on peut utiliser l'un ou l'autre: l'ordre d'initialisation par
rapport aux bases).
__________________
Les MP ne sont pas là pour les questions techniques, les forums sont là pour ça.
Jean-Marc.Bourguet est déconnecté   Envoyer un message privé Réponse avec citation
NEWS C++FAQs C++TUTORIELS C++LIVRES C++OUTILS & COMPILATEURS C++SOURCES C++BLOG C++Qt

Réponse

Précédent   Forum des développeurs > C et C++ > C++



Outils de la discussion

Règles de messages
Vous ne pouvez pas créer de nouvelles discussions
Vous ne pouvez pas envoyer des réponses
Vous ne pouvez pas envoyer des pièces jointes
Vous ne pouvez pas modifier vos messages

Les balises BB sont activées : oui
Les smileys sont activés : oui
La balise [IMG] est activée : oui
Le code HTML peut être employé : non
Trackbacks are non
Pingbacks are non
Refbacks are non
Navigation rapide