OVH Cloud OVH Cloud

POO - Hériter Rectangle d'un Square

42 réponses
Avatar
David FLEURY
Bonjour,

(posté ailleurs mais sans réponse, je tente ma chance ici, étant plus
porté sur le C++)

dans http://www.objectmentor.com/resources/articles/lsp.pdf à propos du
LSP, l'exemple est donné à base d'un Square héritant d'un Rectangle (à
ne pas faire donc)

Auriez-vous une idée d'exemple qui poserait problème pour le cas d'un
Rectangle héritant d'un Square (ou si, à votre avis, le deuxième exemple
que j'ai codé suffit)?

Voici l'argument mis en avant dans l'article (en C++), je reprends
l'exemple donné dans l'article original :
(Un Square héritant d'un rectangle)

// --------------------------------------------------------------------
#include <iostream>

using namespace std;

class Rectangle {
public:
Rectangle( int w = 0, int h = 0 ) : width_(w), height_(h) {}

virtual int width() const { return width_; }
virtual int height() const { return height_; }

virtual void width( int w ) { width_ = w; }
virtual void height( int h ) { height_ = h; }

private:
int width_;
int height_;
};

class Square : public Rectangle {
public:
Square( int w = 0 )
{
width( w );
}

virtual void width( int w ) {
Rectangle::width( w ); Rectangle::height( w );
}

virtual void height( int h ) {
Rectangle::width( h ); Rectangle::height( h );
}
};

// -------- Utilisation du client -------
void g( Rectangle& r ) {
r.width( 5 );
r.height( 4 );

assert( r.width() * r.height() == 20 );
}

int main()
{
Rectangle r( 2, 3 );
g( r );

Square s( 4 );
g( s );
}

Est-ce que celui là pour l'autre sens serait recevable ?
(un Rectangle héritant d'un Square)

// ---------------------------------------------------------------------
#include <iostream>

using namespace std;

class Square {
public:
Square( int w = 0 ) { width( w ); }

int width() const { return width_; }
void width( int w ) { width_ = w; }

virtual double area() const { return width() * width(); }

private:
int width_;
};

class Rectangle : public Square {
public:
Rectangle( int w = 0, int h = 0 )
{
width( w );
height( h );
}

int height() const { return height_; }
void height( int h ) { height_ = h; }

virtual double area() const { return width() * height(); }

private:
int height_;
};

// -------- Utilisation du client -------
void g( Square& r ) {
r.width( 5 );

assert( r.area() == 25 );
}

int main()
{
Square s( 4 );
g( s );

Rectangle r( 2, 3 );
g( r );
}


Cordialement.

10 réponses

1 2 3 4 5
Avatar
espie
In article <jn3fhe$dck$,
Marc Boyer wrote:
Voui, mais il y a la question du choix par défaut.

Les méthodes ne sont pas virtuelles par défaut. Ca m'a été
expliqué comme un principe "don't pay for what you don't use".
Mais il me semble qu'il aurait été équivalent d'utiliser un
mot clef (static ?) pour faire les méthodes non virtuelles,
et aucun mot clef pour les methodes virtuelles (comme Java).



En fait, je vois ca comme une bonne chose.

Les bonnes pratiques ont pas mal evolue, en fait, et les methodes
non virtuelles ont du bon, y compris dans le contexte de l'heritage !

Sutter & Alexandrescu, C++ coding standards:
39: Consider making virtual functions nonpublic, and public functions
nonvirtual.

tu peux aussi chercher NVI: non virtual interface pattern.
Avatar
Marc Boyer
Le 23-04-2012, Marc Espie a écrit :
In article <jn3fhe$dck$,
Marc Boyer wrote:
Voui, mais il y a la question du choix par défaut.

Les méthodes ne sont pas virtuelles par défaut. Ca m'a été
expliqué comme un principe "don't pay for what you don't use".
Mais il me semble qu'il aurait été équivalent d'utiliser un
mot clef (static ?) pour faire les méthodes non virtuelles,
et aucun mot clef pour les methodes virtuelles (comme Java).



En fait, je vois ca comme une bonne chose.

Les bonnes pratiques ont pas mal evolue, en fait, et les methodes
non virtuelles ont du bon, y compris dans le contexte de l'heritage !

Sutter & Alexandrescu, C++ coding standards:
39: Consider making virtual functions nonpublic, and public functions
nonvirtual.



De ce que j'ai compris de ce pattern, l'interface statique fait
les tests de pre-post, et l'implantation est virtuelle.

Et avec ce patern, comment gérer le fait que dans l'héritage,
il y a elargissement/renforcement des pre/post conditions ?

Marc Boyer
--
À mesure que les inégalités regressent, les attentes se renforcent.
François Dubet
Avatar
espie
In article <jn3pgd$hfb$,
Marc Boyer wrote:
Le 23-04-2012, Marc Espie a écrit :
In article <jn3fhe$dck$,
Marc Boyer wrote:
Voui, mais il y a la question du choix par défaut.

Les méthodes ne sont pas virtuelles par défaut. Ca m'a été
expliqué comme un principe "don't pay for what you don't use".
Mais il me semble qu'il aurait été équivalent d'utiliser un
mot clef (static ?) pour faire les méthodes non virtuelles,
et aucun mot clef pour les methodes virtuelles (comme Java).



En fait, je vois ca comme une bonne chose.

Les bonnes pratiques ont pas mal evolue, en fait, et les methodes
non virtuelles ont du bon, y compris dans le contexte de l'heritage !

Sutter & Alexandrescu, C++ coding standards:
39: Consider making virtual functions nonpublic, and public functions
nonvirtual.



De ce que j'ai compris de ce pattern, l'interface statique fait
les tests de pre-post, et l'implantation est virtuelle.

Et avec ce patern, comment gérer le fait que dans l'héritage,
il y a elargissement/renforcement des pre/post conditions ?



Ca veut dire que ca n'avait rien a foutre dans l'interface, et
ca va descendre direct dans l'implementation.
Avatar
Laurent Georget
Le 23/04/2012 12:14, Pascal J. Bourguignon a écrit :
Marc Boyer writes:

La question de la redéfinition est, à mon sens, secondaire. C'est celle
de la validité du code qui est importante. Si je prends le cas d'un
éditeur graphique qui permet de faire une déformation sur une forme,
est-ce que je peux lui passer un carré quand elle attend un rectangle ?



Cette question est un bon exemple.

La réponse dépend complètement des préconditions de l'éditeur graphique.

On peut écrire un éditeur graphique qui plantera, ou qui aura pour
l'utilisateur un comportement ératique, quand on lui passe un carré au
lieu d'un rectangle (même si c'est une sous-classe de rectangle, si
changer_largeur change aussi la hauteur).

Ou on peut écrire un éditeur graphique qui se comportera correctement
dans ce cas, reflétant à l'utilisateur le fait que changer_largeur
change aussi la hauteur.





Oui, je comprends mieux le souci.

On pourrait par exemple demander à la méthode changer_largeur de Carré
de retourner un nouveau Rectangle correctement initialisée lorsqu'elle
est appelée, et créer une autre méthode spécifique au Carré permettant
de changer à la fois hauteur et largeur par exemple. Mais est-ce qu'on
ne devrait pas avoir moyen de "remplacer" un objet par un autre, au lieu
de retourner un résultat ? Les langages objet que je connais (C++ et
Java, et aussi partiellement OCaml) ne sont pas vraiment friands de ce
genre de concepts.
Avatar
Marc Boyer
Le 25-04-2012, Laurent Georget a écrit :
Le 23/04/2012 12:14, Pascal J. Bourguignon a écrit :
Marc Boyer writes:

La question de la redéfinition est, à mon sens, secondaire. C'est celle
de la validité du code qui est importante. Si je prends le cas d'un
éditeur graphique qui permet de faire une déformation sur une forme,
est-ce que je peux lui passer un carré quand elle attend un rectangle ?



Cette question est un bon exemple.

La réponse dépend complètement des préconditions de l'éditeur graphique.

On peut écrire un éditeur graphique qui plantera, ou qui aura pour
l'utilisateur un comportement ératique, quand on lui passe un carré au
lieu d'un rectangle (même si c'est une sous-classe de rectangle, si
changer_largeur change aussi la hauteur).

Ou on peut écrire un éditeur graphique qui se comportera correctement
dans ce cas, reflétant à l'utilisateur le fait que changer_largeur
change aussi la hauteur.




On pourrait par exemple demander à la méthode changer_largeur de Carré
de retourner un nouveau Rectangle correctement initialisée lorsqu'elle
est appelée, et créer une autre méthode spécifique au Carré permettant
de changer à la fois hauteur et largeur par exemple.



C'éait en effet mon idée.

Mais est-ce qu'on
ne devrait pas avoir moyen de "remplacer" un objet par un autre, au lieu
de retourner un résultat ?



Dans ce cas, Carre n'est plus un Rectangle au sens de l'héritage
par substitution.

Les langages objet que je connais (C++ et
Java, et aussi partiellement OCaml) ne sont pas vraiment friands de ce
genre de concepts.



Ah bon ? Je ne vois pas bien ce qui dérange en C++ et Java.

Marc Boyer
--
À mesure que les inégalités regressent, les attentes se renforcent.
François Dubet
Avatar
espie
In article <4f97efe8$0$21490$,
Laurent Georget wrote:
Mais est-ce qu'on
ne devrait pas avoir moyen de "remplacer" un objet par un autre, au lieu
de retourner un résultat ? Les langages objet que je connais (C++ et
Java, et aussi partiellement OCaml) ne sont pas vraiment friands de ce
genre de concepts.



En C++, tu peux faire ce que tu veux. Suffit de rajouter une couche
d'abstraction, et tu peux tres bien avoir un objet qui contiendra au choix
un Rectangle ou un Carre et qui mutera en fonction.

C'est pas "natif" au langage, mais il n'y a pas besoin, ca ne coutera
rien au runtime (evidemment le compilo va morfler par contre).

Confere les QVariant de Qt, par exemple.
Avatar
Pascal J. Bourguignon
Laurent Georget writes:

Le 23/04/2012 12:14, Pascal J. Bourguignon a écrit :
Marc Boyer writes:

La question de la redéfinition est, à mon sens, secondaire. C'est celle
de la validité du code qui est importante. Si je prends le cas d'un
éditeur graphique qui permet de faire une déformation sur une forme,
est-ce que je peux lui passer un carré quand elle attend un rectangle ?



Cette question est un bon exemple.

La réponse dépend complètement des préconditions de l'éditeur graphique.

On peut écrire un éditeur graphique qui plantera, ou qui aura pour
l'utilisateur un comportement ératique, quand on lui passe un carré au
lieu d'un rectangle (même si c'est une sous-classe de rectangle, si
changer_largeur change aussi la hauteur).

Ou on peut écrire un éditeur graphique qui se comportera correctement
dans ce cas, reflétant à l'utilisateur le fait que changer_largeur
change aussi la hauteur.





Oui, je comprends mieux le souci.

On pourrait par exemple demander à la méthode changer_largeur de Carré
de retourner un nouveau Rectangle correctement initialisée lorsqu'elle
est appelée, et créer une autre méthode spécifique au Carré permettant
de changer à la fois hauteur et largeur par exemple. Mais est-ce qu'on
ne devrait pas avoir moyen de "remplacer" un objet par un autre, au lieu
de retourner un résultat ? Les langages objet que je connais (C++ et
Java, et aussi partiellement OCaml) ne sont pas vraiment friands de ce
genre de concepts.



CHANGE-CLASS peut simplifier beaucoup les choses en effet.


Quand on n'a pas la chance de programmer en lisp, on peut utiliser un
"pattern" "Strategy", où on a des objets d'une classe qui ne change pas,
mais quand ils changent de nature, on les associe à une sous-classe
différente représentant la nature particulière.

Concrêtement:

classe Forme
nature: Nature

methode aire
retourner nature.aire()

methode change_largeur (nl)
nature ← nature.change_largeur(nl)


classe Nature

classe Carre sous-classe de Nature
cote

methode aire
retourner cote*cote

methode change_largeur (nl)
si (nl ≠ cote) alors
retourner nouveau Rectangle(nl,cote)
sinon
retourner self


classe Rectangle sous-classe de Nature
largeur
hauteur

methode aire
retourner largeur*hauteur

methode change_largeur (nl)
si (nl = hauteur) alors
retourner nouveau Carre(nl,hauteur)
sinon
largeur ← nl
retourner self

et l'application n'utilise que des instances de Forme, qui peuvent être
de nature changeante.


Exercise: définir les pré- et post-conditions respectant le principe de
susbstitution de Lyskov.


--
__Pascal Bourguignon__ http://www.informatimago.com/
A bad day in () is better than a good day in {}.
Avatar
Marc Boyer
Le 25-04-2012, Marc Espie a écrit :
In article <4f97efe8$0$21490$,
Laurent Georget wrote:
Mais est-ce qu'on
ne devrait pas avoir moyen de "remplacer" un objet par un autre, au lieu
de retourner un résultat ? Les langages objet que je connais (C++ et
Java, et aussi partiellement OCaml) ne sont pas vraiment friands de ce
genre de concepts.



En C++, tu peux faire ce que tu veux. Suffit de rajouter une couche
d'abstraction, et tu peux tres bien avoir un objet qui contiendra au choix
un Rectangle ou un Carre et qui mutera en fonction.

C'est pas "natif" au langage, mais il n'y a pas besoin, ca ne coutera
rien au runtime (evidemment le compilo va morfler par contre).



Enfin, entre avec une méthode qui retourne un nouvel objet
et une autre qui se modifie l'objet contenu, je ne vois pas
de grande différence, hormis le fait de cacher le type
au compilo.

Marc Boyer
--
À mesure que les inégalités regressent, les attentes se renforcent.
François Dubet
Avatar
ld
On 25 avr, 16:56, "Pascal J. Bourguignon"
wrote:
Laurent Georget writes:
> Le 23/04/2012 12:14, Pascal J. Bourguignon a écrit :
>> Marc Boyer writes:

>>> La question de la redéfinition est, à mon sens, secondaire. C'est celle
>>> de la validité du code qui est importante. Si je prends le cas d 'un
>>> éditeur graphique qui permet de faire une déformation sur u ne forme,
>>> est-ce que je peux lui passer un carré quand elle attend un rect angle ?

>> Cette question est un bon exemple.

>> La réponse dépend complètement des préconditions d e l'éditeur graphique.

>> On peut écrire un éditeur graphique qui plantera, ou qui aur a pour
>> l'utilisateur un comportement ératique, quand on lui passe un car ré au
>> lieu d'un rectangle (même si c'est une sous-classe de rectangle, si
>> changer_largeur change aussi la hauteur).

>> Ou on peut écrire un éditeur graphique qui se comportera cor rectement
>> dans ce cas, reflétant à l'utilisateur le fait que changer_l argeur
>> change aussi la hauteur.

> Oui, je comprends mieux le souci.

> On pourrait par exemple demander à la méthode changer_largeur de Carré
> de retourner un nouveau Rectangle correctement initialisée lorsqu' elle
> est appelée, et créer une autre méthode spécifique au Carré permettant
> de changer à la fois hauteur et largeur par exemple. Mais est-ce q u'on
> ne devrait pas avoir moyen de "remplacer" un objet par un autre, au lie u
> de retourner un résultat ? Les langages objet que je connais (C++ et
> Java, et aussi partiellement OCaml) ne sont pas vraiment friands de ce
> genre de concepts.

CHANGE-CLASS peut simplifier beaucoup les choses en effet.

Quand on n'a pas la chance de programmer en lisp,



C'est CLOS et pas Lisp qui permet de faire ca. Dans le meme ordre
d'idee, COS peu le faire alors que ce n'est que du C, avec quelques
contraintes sachant qu'il est statiquement type. C'est la base des
classes cluster pour gerer les etats implicites.

a+, laurent

on peut utiliser un
"pattern" "Strategy", où on a des objets d'une classe qui ne change pas,
mais quand ils changent de nature, on les associe à une sous-classe
différente représentant la nature particulière.

Concrêtement:

classe Forme
   nature: Nature

methode aire
   retourner nature.aire()

methode change_largeur (nl)
   nature ← nature.change_largeur(nl)

classe Nature

classe Carre sous-classe de Nature
   cote

methode aire
   retourner cote*cote

methode change_largeur (nl)
   si (nl ≠ cote) alors
      retourner nouveau Rectangle(nl,cote)
   sinon
      retourner self

classe Rectangle sous-classe de Nature
   largeur
   hauteur

methode aire
   retourner largeur*hauteur

methode change_largeur (nl)
   si (nl = hauteur) alors
      retourner nouveau Carre(nl,hauteur)
   sinon
      largeur ← nl
      retourner self

et l'application n'utilise que des instances de Forme, qui peuvent ê tre
de nature changeante.

Exercise: définir les pré- et post-conditions respectant le pri ncipe de
          susbstitution de Lyskov.

--
__Pascal Bourguignon__                    http://www.informatimago.com/
A bad day in () is better than a good day in {}.
Avatar
espie
In article <jn94np$pg1$,
Marc Boyer wrote:
Le 25-04-2012, Marc Espie a écrit :
In article <4f97efe8$0$21490$,
Laurent Georget wrote:
Mais est-ce qu'on
ne devrait pas avoir moyen de "remplacer" un objet par un autre, au lieu
de retourner un résultat ? Les langages objet que je connais (C++ et
Java, et aussi partiellement OCaml) ne sont pas vraiment friands de ce
genre de concepts.



En C++, tu peux faire ce que tu veux. Suffit de rajouter une couche
d'abstraction, et tu peux tres bien avoir un objet qui contiendra au choix
un Rectangle ou un Carre et qui mutera en fonction.

C'est pas "natif" au langage, mais il n'y a pas besoin, ca ne coutera
rien au runtime (evidemment le compilo va morfler par contre).



Enfin, entre avec une méthode qui retourne un nouvel objet
et une autre qui se modifie l'objet contenu, je ne vois pas
de grande différence, hormis le fait de cacher le type
au compilo.

Marc Boyer



Il n'y en a pas, j'en ai juste ma claque des gnagnagna habituels sur
`ah ca je peux faire avec mon vrai langage objet'. Le truc bien avec C++,
c'est qu'en vrai, ca ne t'impose a peu pres rien sur ce que tu fais, tu
peux inventer la semantique que tu veux pour coller sur ta syntaxe.

(le truc pas bien, c'est que tu peux VRAIMENT faire ce que tu veux, meme
et surtout si c'est n'importe quoi).
1 2 3 4 5