(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)?
(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)?
(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)?
Maintenant, il est vrai qu'il y a quelque chose d'étrange dans la
relation entre carré et rectangle. D'habitude, dans une sous-classe on
a plus d'attributs, car les instances d'une sous-classe sont plus
spécifiques. Mais si on établi Rectangle comme super-classe et Carré
comme sous-classe on obtenient une sous-classe avec "moins" d'attributs
que sa super-classe.
Maintenant, il est vrai qu'il y a quelque chose d'étrange dans la
relation entre carré et rectangle. D'habitude, dans une sous-classe on
a plus d'attributs, car les instances d'une sous-classe sont plus
spécifiques. Mais si on établi Rectangle comme super-classe et Carré
comme sous-classe on obtenient une sous-classe avec "moins" d'attributs
que sa super-classe.
Maintenant, il est vrai qu'il y a quelque chose d'étrange dans la
relation entre carré et rectangle. D'habitude, dans une sous-classe on
a plus d'attributs, car les instances d'une sous-classe sont plus
spécifiques. Mais si on établi Rectangle comme super-classe et Carré
comme sous-classe on obtenient une sous-classe avec "moins" d'attributs
que sa super-classe.
"Pascal J. Bourguignon" wrote:Maintenant, il est vrai qu'il y a quelque chose d'étrange dans la
relation entre carré et rectangle. D'habitude, dans une sous-classe on
a plus d'attributs, car les instances d'une sous-classe sont plus
spécifiques. Mais si on établi Rectangle comme super-classe et Carré
comme sous-classe on obtenient une sous-classe avec "moins" d'attributs
que sa super-classe.
C'est assez courant que la relation "être un cas particulier de" et la
relation "contenir un" soient inversées (nombres réels vs complexes,
entiers vs rationnels, etc), ce qui rend parfois les choses peu
pratiques dans les langages comme le C++ où l'héritage implique le
second.
"Pascal J. Bourguignon" wrote:
Maintenant, il est vrai qu'il y a quelque chose d'étrange dans la
relation entre carré et rectangle. D'habitude, dans une sous-classe on
a plus d'attributs, car les instances d'une sous-classe sont plus
spécifiques. Mais si on établi Rectangle comme super-classe et Carré
comme sous-classe on obtenient une sous-classe avec "moins" d'attributs
que sa super-classe.
C'est assez courant que la relation "être un cas particulier de" et la
relation "contenir un" soient inversées (nombres réels vs complexes,
entiers vs rationnels, etc), ce qui rend parfois les choses peu
pratiques dans les langages comme le C++ où l'héritage implique le
second.
"Pascal J. Bourguignon" wrote:Maintenant, il est vrai qu'il y a quelque chose d'étrange dans la
relation entre carré et rectangle. D'habitude, dans une sous-classe on
a plus d'attributs, car les instances d'une sous-classe sont plus
spécifiques. Mais si on établi Rectangle comme super-classe et Carré
comme sous-classe on obtenient une sous-classe avec "moins" d'attributs
que sa super-classe.
C'est assez courant que la relation "être un cas particulier de" et la
relation "contenir un" soient inversées (nombres réels vs complexes,
entiers vs rationnels, etc), ce qui rend parfois les choses peu
pratiques dans les langages comme le C++ où l'héritage implique le
second.
Premier cas:
classe Rectangle
largeur
hauteur
méthode Rectangle::changer_largeur (nouvelle_largeur)
pre-condition: nouvelle_largeur = α ∧ α> 0 [Pc]
post-condition: self.largeur = α [Qc]
classe Carré, sous-classe de Rectangle
méthode Carré::changer_largeur (nouvelle_largeur)
pre-condition: nouvelle_largeur = α ∧ α> 0 [Ps]
post-condition: self.largeur = α ∧ self.hauteur = α [Qs]
Deuxième cas:
classe Carré
côté
méthode Carré::changer_côté (nouveau_côté)
pre-condition: nouveau_côté = α ∧ α> 0 [Ps]
post-condition: self.côte = α [Qs]
classe Rectangle
largeur
hauteur
méthode Rectangle::changer_côté (nouveau_côté)
pre-condition: nouveau_côté = α ∧ α> 0 [Ps]
post-condition: self.côte = α ∧ self.largeur = α
∧ self.hauteur = α [Qs]
Preuve du principe de substitution:
Pc ⇐ Ps ∧ Qs ⇒ Ps
⇔ nouveau_côté = α ∧ α> 0 ⇐ nouveau_côté = α ∧ α> 0
∧ (self.côte = α ∧ self.largeur = α ∧ self.hauteur = α) ⇒ self.côté = α
⇔ Vrai
Donc ça marche aussi. Rien ne nous empêche maintenant de définir des
méthodes spécifiques à la sous-classe:
méthode Rectangle::changer_largeur (nouvelle_largeur)
pre-condition: nouvelle_largeur = α ∧ α> 0
∧ hauteur = θ [Ps]
post-condition: self.côte = max(α,θ)
∧ self.largeur = α
∧ self.hauteur = θ [Qs]
En conclusion: il faut définir les pré- et post-conditions de chaque
méthode (et les invariants de chaque classe, mais c'est une autre
question).
Premier cas:
classe Rectangle
largeur
hauteur
méthode Rectangle::changer_largeur (nouvelle_largeur)
pre-condition: nouvelle_largeur = α ∧ α> 0 [Pc]
post-condition: self.largeur = α [Qc]
classe Carré, sous-classe de Rectangle
méthode Carré::changer_largeur (nouvelle_largeur)
pre-condition: nouvelle_largeur = α ∧ α> 0 [Ps]
post-condition: self.largeur = α ∧ self.hauteur = α [Qs]
Deuxième cas:
classe Carré
côté
méthode Carré::changer_côté (nouveau_côté)
pre-condition: nouveau_côté = α ∧ α> 0 [Ps]
post-condition: self.côte = α [Qs]
classe Rectangle
largeur
hauteur
méthode Rectangle::changer_côté (nouveau_côté)
pre-condition: nouveau_côté = α ∧ α> 0 [Ps]
post-condition: self.côte = α ∧ self.largeur = α
∧ self.hauteur = α [Qs]
Preuve du principe de substitution:
Pc ⇐ Ps ∧ Qs ⇒ Ps
⇔ nouveau_côté = α ∧ α> 0 ⇐ nouveau_côté = α ∧ α> 0
∧ (self.côte = α ∧ self.largeur = α ∧ self.hauteur = α) ⇒ self.côté = α
⇔ Vrai
Donc ça marche aussi. Rien ne nous empêche maintenant de définir des
méthodes spécifiques à la sous-classe:
méthode Rectangle::changer_largeur (nouvelle_largeur)
pre-condition: nouvelle_largeur = α ∧ α> 0
∧ hauteur = θ [Ps]
post-condition: self.côte = max(α,θ)
∧ self.largeur = α
∧ self.hauteur = θ [Qs]
En conclusion: il faut définir les pré- et post-conditions de chaque
méthode (et les invariants de chaque classe, mais c'est une autre
question).
Premier cas:
classe Rectangle
largeur
hauteur
méthode Rectangle::changer_largeur (nouvelle_largeur)
pre-condition: nouvelle_largeur = α ∧ α> 0 [Pc]
post-condition: self.largeur = α [Qc]
classe Carré, sous-classe de Rectangle
méthode Carré::changer_largeur (nouvelle_largeur)
pre-condition: nouvelle_largeur = α ∧ α> 0 [Ps]
post-condition: self.largeur = α ∧ self.hauteur = α [Qs]
Deuxième cas:
classe Carré
côté
méthode Carré::changer_côté (nouveau_côté)
pre-condition: nouveau_côté = α ∧ α> 0 [Ps]
post-condition: self.côte = α [Qs]
classe Rectangle
largeur
hauteur
méthode Rectangle::changer_côté (nouveau_côté)
pre-condition: nouveau_côté = α ∧ α> 0 [Ps]
post-condition: self.côte = α ∧ self.largeur = α
∧ self.hauteur = α [Qs]
Preuve du principe de substitution:
Pc ⇐ Ps ∧ Qs ⇒ Ps
⇔ nouveau_côté = α ∧ α> 0 ⇐ nouveau_côté = α ∧ α> 0
∧ (self.côte = α ∧ self.largeur = α ∧ self.hauteur = α) ⇒ self.côté = α
⇔ Vrai
Donc ça marche aussi. Rien ne nous empêche maintenant de définir des
méthodes spécifiques à la sous-classe:
méthode Rectangle::changer_largeur (nouvelle_largeur)
pre-condition: nouvelle_largeur = α ∧ α> 0
∧ hauteur = θ [Ps]
post-condition: self.côte = max(α,θ)
∧ self.largeur = α
∧ self.hauteur = θ [Qs]
En conclusion: il faut définir les pré- et post-conditions de chaque
méthode (et les invariants de chaque classe, mais c'est une autre
question).
Marc writes:"Pascal J. Bourguignon" wrote:Maintenant, il est vrai qu'il y a quelque chose d'étrange dans la
relation entre carré et rectangle. D'habitude, dans une sous-classe on
a plus d'attributs, car les instances d'une sous-classe sont plus
spécifiques. Mais si on établi Rectangle comme super-classe et Carré
comme sous-classe on obtenient une sous-classe avec "moins" d'attributs
que sa super-classe.
C'est assez courant que la relation "être un cas particulier de" et la
relation "contenir un" soient inversées (nombres réels vs complexes,
entiers vs rationnels, etc), ce qui rend parfois les choses peu
pratiques dans les langages comme le C++ où l'héritage implique le
second.
On peut toujours créer une super-classe/sur-ensemble.
Mais il faut considérer que un Rectangle tel que largeur=hauteur doit
être égal à un carré de même côté. La question est alors de savoir si
on interne les objets "égaux" ou si on accepte des duplicatats.
Aussi, certains langages permettent de changer la classe d'un objet en
cours de route, ça peut aider. Ainsi, en Common Lisp, si un rectangle
obtenient une largeur égale à sa hauteur, on peut lui faire changer de
classe pour devenir carré, et vice-versa. Ça simplifie ce genre de
problème.
Marc <marc.glisse@gmail.com> writes:
"Pascal J. Bourguignon" wrote:
Maintenant, il est vrai qu'il y a quelque chose d'étrange dans la
relation entre carré et rectangle. D'habitude, dans une sous-classe on
a plus d'attributs, car les instances d'une sous-classe sont plus
spécifiques. Mais si on établi Rectangle comme super-classe et Carré
comme sous-classe on obtenient une sous-classe avec "moins" d'attributs
que sa super-classe.
C'est assez courant que la relation "être un cas particulier de" et la
relation "contenir un" soient inversées (nombres réels vs complexes,
entiers vs rationnels, etc), ce qui rend parfois les choses peu
pratiques dans les langages comme le C++ où l'héritage implique le
second.
On peut toujours créer une super-classe/sur-ensemble.
Mais il faut considérer que un Rectangle tel que largeur=hauteur doit
être égal à un carré de même côté. La question est alors de savoir si
on interne les objets "égaux" ou si on accepte des duplicatats.
Aussi, certains langages permettent de changer la classe d'un objet en
cours de route, ça peut aider. Ainsi, en Common Lisp, si un rectangle
obtenient une largeur égale à sa hauteur, on peut lui faire changer de
classe pour devenir carré, et vice-versa. Ça simplifie ce genre de
problème.
Marc writes:"Pascal J. Bourguignon" wrote:Maintenant, il est vrai qu'il y a quelque chose d'étrange dans la
relation entre carré et rectangle. D'habitude, dans une sous-classe on
a plus d'attributs, car les instances d'une sous-classe sont plus
spécifiques. Mais si on établi Rectangle comme super-classe et Carré
comme sous-classe on obtenient une sous-classe avec "moins" d'attributs
que sa super-classe.
C'est assez courant que la relation "être un cas particulier de" et la
relation "contenir un" soient inversées (nombres réels vs complexes,
entiers vs rationnels, etc), ce qui rend parfois les choses peu
pratiques dans les langages comme le C++ où l'héritage implique le
second.
On peut toujours créer une super-classe/sur-ensemble.
Mais il faut considérer que un Rectangle tel que largeur=hauteur doit
être égal à un carré de même côté. La question est alors de savoir si
on interne les objets "égaux" ou si on accepte des duplicatats.
Aussi, certains langages permettent de changer la classe d'un objet en
cours de route, ça peut aider. Ainsi, en Common Lisp, si un rectangle
obtenient une largeur égale à sa hauteur, on peut lui faire changer de
classe pour devenir carré, et vice-versa. Ça simplifie ce genre de
problème.
Premier cas:
classe Rectangle
largeur
hauteur
méthode Rectangle::changer_largeur (nouvelle_largeur)
pre-condition: nouvelle_largeur = α ∧ α> 0 [Pc]
post-condition: self.largeur = α [Qc]
post-condition = self.largeur = α ∧ self.hauteur = self.ancienne_hauteur
(Je ne sais pas écrire que la hauteur n'a pas été affectée par la
méthode changer_largeur)
classe Carré, sous-classe de Rectangle
méthode Carré::changer_largeur (nouvelle_largeur)
pre-condition: nouvelle_largeur = α ∧ α> 0 [Ps]
post-condition: self.largeur = α ∧ self.hauteur = α [Qs]
Maintenant, le principe n'est plus vrai que la fonction modifie la
hauteur ce qui est contraire à la post-condition de la classe mère.
Deuxième cas:
classe Carré
côté
méthode Carré::changer_côté (nouveau_côté)
pre-condition: nouveau_côté = α ∧ α> 0 [Ps]
post-condition: self.côte = α [Qs]
classe Rectangle
largeur
hauteur
méthode Rectangle::changer_côté (nouveau_côté)
pre-condition: nouveau_côté = α ∧ α> 0 [Ps]
post-condition: self.côte = α ∧ self.largeur = α
∧ self.hauteur = α [Qs]
C'est pour cet exemple, où j'ai plus de mal à trouver une
post-condition qui ne soit pas valable en n'ayant à disposition que
l'interface de la classe Carré (encore plus avec une implémentation
séparant côté et largeur/hauteur - même si je ne suis plus sûr de voir
l'intérêt pour une dérivation dans ce cas, du code devant être
dupliqué pour maintenir la cohérence entre ses membres.)
Preuve du principe de substitution:
Pc ⇐ Ps ∧ Qs ⇒ Ps
⇔ nouveau_côté = α ∧ α> 0 ⇐ nouveau_côté = α ∧ α> 0
∧ (self.côte = α ∧ self.largeur = α ∧ self.hauteur = α) ⇒ self.côté = α
⇔ Vrai
Donc ça marche aussi. Rien ne nous empêche maintenant de définir des
méthodes spécifiques à la sous-classe:
méthode Rectangle::changer_largeur (nouvelle_largeur)
pre-condition: nouvelle_largeur = α ∧ α> 0
∧ hauteur = θ [Ps]
post-condition: self.côte = max(α,θ)
∧ self.largeur = α
∧ self.hauteur = θ [Qs]
C'est là, où une post-condition (un invariant) de classe invaliderait
cette hiérarchie.
post-conditions : angle(self.diagonales)= 90° ∧ area( côté * côté)
En conclusion: il faut définir les pré- et post-conditions de chaque
méthode (et les invariants de chaque classe, mais c'est une autre
question).
Ah, je n'avais pas vu que l'invariant de classe était évoqué ici.
Tous les définir, ça risque de prendre du temps...
Donc, j'ai l'impression qu'une hiérarchie de classe se valide par ses
pré/post conditions et invariant, qui en valide ainsi l'usage qu'on en
fait.
Premier cas:
classe Rectangle
largeur
hauteur
méthode Rectangle::changer_largeur (nouvelle_largeur)
pre-condition: nouvelle_largeur = α ∧ α> 0 [Pc]
post-condition: self.largeur = α [Qc]
post-condition = self.largeur = α ∧ self.hauteur = self.ancienne_hauteur
(Je ne sais pas écrire que la hauteur n'a pas été affectée par la
méthode changer_largeur)
classe Carré, sous-classe de Rectangle
méthode Carré::changer_largeur (nouvelle_largeur)
pre-condition: nouvelle_largeur = α ∧ α> 0 [Ps]
post-condition: self.largeur = α ∧ self.hauteur = α [Qs]
Maintenant, le principe n'est plus vrai que la fonction modifie la
hauteur ce qui est contraire à la post-condition de la classe mère.
Deuxième cas:
classe Carré
côté
méthode Carré::changer_côté (nouveau_côté)
pre-condition: nouveau_côté = α ∧ α> 0 [Ps]
post-condition: self.côte = α [Qs]
classe Rectangle
largeur
hauteur
méthode Rectangle::changer_côté (nouveau_côté)
pre-condition: nouveau_côté = α ∧ α> 0 [Ps]
post-condition: self.côte = α ∧ self.largeur = α
∧ self.hauteur = α [Qs]
C'est pour cet exemple, où j'ai plus de mal à trouver une
post-condition qui ne soit pas valable en n'ayant à disposition que
l'interface de la classe Carré (encore plus avec une implémentation
séparant côté et largeur/hauteur - même si je ne suis plus sûr de voir
l'intérêt pour une dérivation dans ce cas, du code devant être
dupliqué pour maintenir la cohérence entre ses membres.)
Preuve du principe de substitution:
Pc ⇐ Ps ∧ Qs ⇒ Ps
⇔ nouveau_côté = α ∧ α> 0 ⇐ nouveau_côté = α ∧ α> 0
∧ (self.côte = α ∧ self.largeur = α ∧ self.hauteur = α) ⇒ self.côté = α
⇔ Vrai
Donc ça marche aussi. Rien ne nous empêche maintenant de définir des
méthodes spécifiques à la sous-classe:
méthode Rectangle::changer_largeur (nouvelle_largeur)
pre-condition: nouvelle_largeur = α ∧ α> 0
∧ hauteur = θ [Ps]
post-condition: self.côte = max(α,θ)
∧ self.largeur = α
∧ self.hauteur = θ [Qs]
C'est là, où une post-condition (un invariant) de classe invaliderait
cette hiérarchie.
post-conditions : angle(self.diagonales)= 90° ∧ area( côté * côté)
En conclusion: il faut définir les pré- et post-conditions de chaque
méthode (et les invariants de chaque classe, mais c'est une autre
question).
Ah, je n'avais pas vu que l'invariant de classe était évoqué ici.
Tous les définir, ça risque de prendre du temps...
Donc, j'ai l'impression qu'une hiérarchie de classe se valide par ses
pré/post conditions et invariant, qui en valide ainsi l'usage qu'on en
fait.
Premier cas:
classe Rectangle
largeur
hauteur
méthode Rectangle::changer_largeur (nouvelle_largeur)
pre-condition: nouvelle_largeur = α ∧ α> 0 [Pc]
post-condition: self.largeur = α [Qc]
post-condition = self.largeur = α ∧ self.hauteur = self.ancienne_hauteur
(Je ne sais pas écrire que la hauteur n'a pas été affectée par la
méthode changer_largeur)
classe Carré, sous-classe de Rectangle
méthode Carré::changer_largeur (nouvelle_largeur)
pre-condition: nouvelle_largeur = α ∧ α> 0 [Ps]
post-condition: self.largeur = α ∧ self.hauteur = α [Qs]
Maintenant, le principe n'est plus vrai que la fonction modifie la
hauteur ce qui est contraire à la post-condition de la classe mère.
Deuxième cas:
classe Carré
côté
méthode Carré::changer_côté (nouveau_côté)
pre-condition: nouveau_côté = α ∧ α> 0 [Ps]
post-condition: self.côte = α [Qs]
classe Rectangle
largeur
hauteur
méthode Rectangle::changer_côté (nouveau_côté)
pre-condition: nouveau_côté = α ∧ α> 0 [Ps]
post-condition: self.côte = α ∧ self.largeur = α
∧ self.hauteur = α [Qs]
C'est pour cet exemple, où j'ai plus de mal à trouver une
post-condition qui ne soit pas valable en n'ayant à disposition que
l'interface de la classe Carré (encore plus avec une implémentation
séparant côté et largeur/hauteur - même si je ne suis plus sûr de voir
l'intérêt pour une dérivation dans ce cas, du code devant être
dupliqué pour maintenir la cohérence entre ses membres.)
Preuve du principe de substitution:
Pc ⇐ Ps ∧ Qs ⇒ Ps
⇔ nouveau_côté = α ∧ α> 0 ⇐ nouveau_côté = α ∧ α> 0
∧ (self.côte = α ∧ self.largeur = α ∧ self.hauteur = α) ⇒ self.côté = α
⇔ Vrai
Donc ça marche aussi. Rien ne nous empêche maintenant de définir des
méthodes spécifiques à la sous-classe:
méthode Rectangle::changer_largeur (nouvelle_largeur)
pre-condition: nouvelle_largeur = α ∧ α> 0
∧ hauteur = θ [Ps]
post-condition: self.côte = max(α,θ)
∧ self.largeur = α
∧ self.hauteur = θ [Qs]
C'est là, où une post-condition (un invariant) de classe invaliderait
cette hiérarchie.
post-conditions : angle(self.diagonales)= 90° ∧ area( côté * côté)
En conclusion: il faut définir les pré- et post-conditions de chaque
méthode (et les invariants de chaque classe, mais c'est une autre
question).
Ah, je n'avais pas vu que l'invariant de classe était évoqué ici.
Tous les définir, ça risque de prendre du temps...
Donc, j'ai l'impression qu'une hiérarchie de classe se valide par ses
pré/post conditions et invariant, qui en valide ainsi l'usage qu'on en
fait.
Oui, mais on change alors de paradigme de programmation puisque le lisp
s'appuie sur une vision fonctionnelle des choses.
Pour ma part, j'ai toujours appris qu'une relation d'héritage n'est valide
que si elle a un sens sémantiquement.
Par exemple, on peut faire hériter Voiture de Véhicule car une Voiture
est une spécialisation de Véhicule (un cas d'école longtemps
rabaché...:) ). Mais dans le cas des objets mathématiques ;
effectivement ça ne marche plus vraiment.
Je pense cependant que la relation "Carré hérite de Rectangle" a plus de
sens. Par exemple, si jamais on voulait étendre la classe pour
introduire le calcul d'aire. La relation valable pour le rectangle
A=hauteur * largeur resterait valide pour le carré (A=côté*côté),
tandis qu'une modélisation inverse rendrait nécessaire la redéfinition
de la méthode pour le rectangle. Et, évidemment, c'est le cas pour pas
mal de propriétés du rectangle. Donc, pour garantir la maintenabilité,
je privilégierai ce choix d'héritage.
Avez-vous des exemples de méthodes qui fonctionnent dans l'autre sens ?
C'est-à-dire, en supposant que Carré hérite de Rectangle, des méthodes
qui sont établies pour le carré et reste valide pour le rectangle par
héritage, alors que dans la modélisation inverse, elle nécessiterait une
redéfinition ?
Oui, mais on change alors de paradigme de programmation puisque le lisp
s'appuie sur une vision fonctionnelle des choses.
Pour ma part, j'ai toujours appris qu'une relation d'héritage n'est valide
que si elle a un sens sémantiquement.
Par exemple, on peut faire hériter Voiture de Véhicule car une Voiture
est une spécialisation de Véhicule (un cas d'école longtemps
rabaché...:) ). Mais dans le cas des objets mathématiques ;
effectivement ça ne marche plus vraiment.
Je pense cependant que la relation "Carré hérite de Rectangle" a plus de
sens. Par exemple, si jamais on voulait étendre la classe pour
introduire le calcul d'aire. La relation valable pour le rectangle
A=hauteur * largeur resterait valide pour le carré (A=côté*côté),
tandis qu'une modélisation inverse rendrait nécessaire la redéfinition
de la méthode pour le rectangle. Et, évidemment, c'est le cas pour pas
mal de propriétés du rectangle. Donc, pour garantir la maintenabilité,
je privilégierai ce choix d'héritage.
Avez-vous des exemples de méthodes qui fonctionnent dans l'autre sens ?
C'est-à-dire, en supposant que Carré hérite de Rectangle, des méthodes
qui sont établies pour le carré et reste valide pour le rectangle par
héritage, alors que dans la modélisation inverse, elle nécessiterait une
redéfinition ?
Oui, mais on change alors de paradigme de programmation puisque le lisp
s'appuie sur une vision fonctionnelle des choses.
Pour ma part, j'ai toujours appris qu'une relation d'héritage n'est valide
que si elle a un sens sémantiquement.
Par exemple, on peut faire hériter Voiture de Véhicule car une Voiture
est une spécialisation de Véhicule (un cas d'école longtemps
rabaché...:) ). Mais dans le cas des objets mathématiques ;
effectivement ça ne marche plus vraiment.
Je pense cependant que la relation "Carré hérite de Rectangle" a plus de
sens. Par exemple, si jamais on voulait étendre la classe pour
introduire le calcul d'aire. La relation valable pour le rectangle
A=hauteur * largeur resterait valide pour le carré (A=côté*côté),
tandis qu'une modélisation inverse rendrait nécessaire la redéfinition
de la méthode pour le rectangle. Et, évidemment, c'est le cas pour pas
mal de propriétés du rectangle. Donc, pour garantir la maintenabilité,
je privilégierai ce choix d'héritage.
Avez-vous des exemples de méthodes qui fonctionnent dans l'autre sens ?
C'est-à-dire, en supposant que Carré hérite de Rectangle, des méthodes
qui sont établies pour le carré et reste valide pour le rectangle par
héritage, alors que dans la modélisation inverse, elle nécessiterait une
redéfinition ?
classe Rectangle
largeur
hauteur
méthode Rectangle::changer_largeur (nouvelle_largeur)
pre-condition: nouvelle_largeur = α ∧ α> 0 [Pc]
post-condition: self.largeur = α [Qc]
classe Carré, sous-classe de Rectangle
méthode Carré::changer_largeur (nouvelle_largeur)
pre-condition: nouvelle_largeur = α ∧ α> 0 [Ps]
post-condition: self.largeur = α ∧ self.hauteur = α [Qs]
Si tu introduit des couples pré- et post-conditions qui sont incohérents
avec tes définitions, c'est ton problème. C'est à toi de choisir ce que
tu veux dire, et de t'assurer de ce que tu dis soit cohérent.
C'est comme la déclaration des types; Si tu écris:
{
int x;
x="hello";
}
c'est toi qui vois. Comme tu écris une incohérence, un compilateur ou
un vérificateur peut la détecter, et c'est à toi de choisir si tu
changes le type ou si tu change la valeur.
classe Rectangle
largeur
hauteur
méthode Rectangle::changer_largeur (nouvelle_largeur)
pre-condition: nouvelle_largeur = α ∧ α> 0 [Pc]
post-condition: self.largeur = α [Qc]
classe Carré, sous-classe de Rectangle
méthode Carré::changer_largeur (nouvelle_largeur)
pre-condition: nouvelle_largeur = α ∧ α> 0 [Ps]
post-condition: self.largeur = α ∧ self.hauteur = α [Qs]
Si tu introduit des couples pré- et post-conditions qui sont incohérents
avec tes définitions, c'est ton problème. C'est à toi de choisir ce que
tu veux dire, et de t'assurer de ce que tu dis soit cohérent.
C'est comme la déclaration des types; Si tu écris:
{
int x;
x="hello";
}
c'est toi qui vois. Comme tu écris une incohérence, un compilateur ou
un vérificateur peut la détecter, et c'est à toi de choisir si tu
changes le type ou si tu change la valeur.
classe Rectangle
largeur
hauteur
méthode Rectangle::changer_largeur (nouvelle_largeur)
pre-condition: nouvelle_largeur = α ∧ α> 0 [Pc]
post-condition: self.largeur = α [Qc]
classe Carré, sous-classe de Rectangle
méthode Carré::changer_largeur (nouvelle_largeur)
pre-condition: nouvelle_largeur = α ∧ α> 0 [Ps]
post-condition: self.largeur = α ∧ self.hauteur = α [Qs]
Si tu introduit des couples pré- et post-conditions qui sont incohérents
avec tes définitions, c'est ton problème. C'est à toi de choisir ce que
tu veux dire, et de t'assurer de ce que tu dis soit cohérent.
C'est comme la déclaration des types; Si tu écris:
{
int x;
x="hello";
}
c'est toi qui vois. Comme tu écris une incohérence, un compilateur ou
un vérificateur peut la détecter, et c'est à toi de choisir si tu
changes le type ou si tu change la valeur.
Si tu introduit des couples pré- et post-conditions qui sont incohérents
avec tes définitions, c'est ton problème. C'est à toi de choisir ce que
tu veux dire, et de t'assurer de ce que tu dis soit cohérent.
Le problème viendrait plutôt de quelqu'un qui introduit une nouvelle
classe dans l'arbre d'héritage. A priori, l'auteur sait (ou a su)
utiliser ces classes.
Les pré/post conditions étant implicite, et dans ce cas, on peut juste
supposer qu'elles proviennent du modèle fonctionnelle.
Dans mon exemple, je dirais que ni le carré ni le rectangle ne peuvent
hériter l'un de l'autre sans qu'on puisse trouver un cas d'utilisation
ne cassant pas le principe de substitution. Par contre, il existe des
domaines d'utilisation, ou il est raisonnable de le faire, dans ce
cas, il semble judicieux (sauf à avoir des problèmes qui arriveront
avec l'ajout de classe dans l'arbre) de définir clairement les
pré/post conditions.
C'est comme la déclaration des types; Si tu écris:
{
int x;
x="hello";
}
c'est toi qui vois. Comme tu écris une incohérence, un compilateur ou
un vérificateur peut la détecter, et c'est à toi de choisir si tu
changes le type ou si tu change la valeur.
Pour changer de type, va falloir changer de langage.
De toute façon, je trouve un peu trop "contre nature" un changement de
type...
En tout cas, j'ai l'impression que c'est un sujet encore bien délicat,
ou les théoriciens ne semblent pas tous d'accord, et qu'en pratique on
arrive à bien vivre sans.
Si tu introduit des couples pré- et post-conditions qui sont incohérents
avec tes définitions, c'est ton problème. C'est à toi de choisir ce que
tu veux dire, et de t'assurer de ce que tu dis soit cohérent.
Le problème viendrait plutôt de quelqu'un qui introduit une nouvelle
classe dans l'arbre d'héritage. A priori, l'auteur sait (ou a su)
utiliser ces classes.
Les pré/post conditions étant implicite, et dans ce cas, on peut juste
supposer qu'elles proviennent du modèle fonctionnelle.
Dans mon exemple, je dirais que ni le carré ni le rectangle ne peuvent
hériter l'un de l'autre sans qu'on puisse trouver un cas d'utilisation
ne cassant pas le principe de substitution. Par contre, il existe des
domaines d'utilisation, ou il est raisonnable de le faire, dans ce
cas, il semble judicieux (sauf à avoir des problèmes qui arriveront
avec l'ajout de classe dans l'arbre) de définir clairement les
pré/post conditions.
C'est comme la déclaration des types; Si tu écris:
{
int x;
x="hello";
}
c'est toi qui vois. Comme tu écris une incohérence, un compilateur ou
un vérificateur peut la détecter, et c'est à toi de choisir si tu
changes le type ou si tu change la valeur.
Pour changer de type, va falloir changer de langage.
De toute façon, je trouve un peu trop "contre nature" un changement de
type...
En tout cas, j'ai l'impression que c'est un sujet encore bien délicat,
ou les théoriciens ne semblent pas tous d'accord, et qu'en pratique on
arrive à bien vivre sans.
Si tu introduit des couples pré- et post-conditions qui sont incohérents
avec tes définitions, c'est ton problème. C'est à toi de choisir ce que
tu veux dire, et de t'assurer de ce que tu dis soit cohérent.
Le problème viendrait plutôt de quelqu'un qui introduit une nouvelle
classe dans l'arbre d'héritage. A priori, l'auteur sait (ou a su)
utiliser ces classes.
Les pré/post conditions étant implicite, et dans ce cas, on peut juste
supposer qu'elles proviennent du modèle fonctionnelle.
Dans mon exemple, je dirais que ni le carré ni le rectangle ne peuvent
hériter l'un de l'autre sans qu'on puisse trouver un cas d'utilisation
ne cassant pas le principe de substitution. Par contre, il existe des
domaines d'utilisation, ou il est raisonnable de le faire, dans ce
cas, il semble judicieux (sauf à avoir des problèmes qui arriveront
avec l'ajout de classe dans l'arbre) de définir clairement les
pré/post conditions.
C'est comme la déclaration des types; Si tu écris:
{
int x;
x="hello";
}
c'est toi qui vois. Comme tu écris une incohérence, un compilateur ou
un vérificateur peut la détecter, et c'est à toi de choisir si tu
changes le type ou si tu change la valeur.
Pour changer de type, va falloir changer de langage.
De toute façon, je trouve un peu trop "contre nature" un changement de
type...
En tout cas, j'ai l'impression que c'est un sujet encore bien délicat,
ou les théoriciens ne semblent pas tous d'accord, et qu'en pratique on
arrive à bien vivre sans.
Laurent Georget - slrn writes:Oui, mais on change alors de paradigme de programmation puisque le lisp
s'appuie sur une vision fonctionnelle des choses.
Non, pas du tout. Lisp est multi-paradigme. Tu choisis pour chaque
bout de code avec quel paradigme tu l'écris.
Pour ma part, j'ai toujours appris qu'une relation d'héritage n'est valide
que si elle a un sens sémantiquement.
La sémantique formelle se mord la queue: elle est définie à partir de
modèles qui sont aussi des systèmes formels. Il n'y a donc pas de
résolution.Par exemple, on peut faire hériter Voiture de Véhicule car une Voiture
est une spécialisation de Véhicule (un cas d'école longtemps
rabaché...:) ). Mais dans le cas des objets mathématiques ;
effectivement ça ne marche plus vraiment.
On peut aussi s'amuser à faire hériter Véhicule de Voiture. Après tout,
un Véhicule comme un Avion, c'est une sorte de voiture avec des ailes,
ou un Véhicule comme un Bateau, c'est une sorte de voiture qui flotte.
Je pense cependant que la relation "Carré hérite de Rectangle" a plus de
sens. Par exemple, si jamais on voulait étendre la classe pour
introduire le calcul d'aire. La relation valable pour le rectangle
A=hauteur * largeur resterait valide pour le carré (A=côté*côté),
tandis qu'une modélisation inverse rendrait nécessaire la redéfinition
de la méthode pour le rectangle. Et, évidemment, c'est le cas pour pas
mal de propriétés du rectangle. Donc, pour garantir la maintenabilité,
je privilégierai ce choix d'héritage.
Tout dépend des pré- et post-conditions que tu choisis. Si tu n'en
écris aucune, n'importe quoi est possible.
Avez-vous des exemples de méthodes qui fonctionnent dans l'autre sens ?
C'est-à-dire, en supposant que Carré hérite de Rectangle, des méthodes
qui sont établies pour le carré et reste valide pour le rectangle par
héritage, alors que dans la modélisation inverse, elle nécessiterait une
redéfinition ?
Par exemple: déplacer, faire_tourner, colorier, etc.
Laurent Georget - slrn <laurent@lgeorget.eu> writes:
Oui, mais on change alors de paradigme de programmation puisque le lisp
s'appuie sur une vision fonctionnelle des choses.
Non, pas du tout. Lisp est multi-paradigme. Tu choisis pour chaque
bout de code avec quel paradigme tu l'écris.
Pour ma part, j'ai toujours appris qu'une relation d'héritage n'est valide
que si elle a un sens sémantiquement.
La sémantique formelle se mord la queue: elle est définie à partir de
modèles qui sont aussi des systèmes formels. Il n'y a donc pas de
résolution.
Par exemple, on peut faire hériter Voiture de Véhicule car une Voiture
est une spécialisation de Véhicule (un cas d'école longtemps
rabaché...:) ). Mais dans le cas des objets mathématiques ;
effectivement ça ne marche plus vraiment.
On peut aussi s'amuser à faire hériter Véhicule de Voiture. Après tout,
un Véhicule comme un Avion, c'est une sorte de voiture avec des ailes,
ou un Véhicule comme un Bateau, c'est une sorte de voiture qui flotte.
Je pense cependant que la relation "Carré hérite de Rectangle" a plus de
sens. Par exemple, si jamais on voulait étendre la classe pour
introduire le calcul d'aire. La relation valable pour le rectangle
A=hauteur * largeur resterait valide pour le carré (A=côté*côté),
tandis qu'une modélisation inverse rendrait nécessaire la redéfinition
de la méthode pour le rectangle. Et, évidemment, c'est le cas pour pas
mal de propriétés du rectangle. Donc, pour garantir la maintenabilité,
je privilégierai ce choix d'héritage.
Tout dépend des pré- et post-conditions que tu choisis. Si tu n'en
écris aucune, n'importe quoi est possible.
Avez-vous des exemples de méthodes qui fonctionnent dans l'autre sens ?
C'est-à-dire, en supposant que Carré hérite de Rectangle, des méthodes
qui sont établies pour le carré et reste valide pour le rectangle par
héritage, alors que dans la modélisation inverse, elle nécessiterait une
redéfinition ?
Par exemple: déplacer, faire_tourner, colorier, etc.
Laurent Georget - slrn writes:Oui, mais on change alors de paradigme de programmation puisque le lisp
s'appuie sur une vision fonctionnelle des choses.
Non, pas du tout. Lisp est multi-paradigme. Tu choisis pour chaque
bout de code avec quel paradigme tu l'écris.
Pour ma part, j'ai toujours appris qu'une relation d'héritage n'est valide
que si elle a un sens sémantiquement.
La sémantique formelle se mord la queue: elle est définie à partir de
modèles qui sont aussi des systèmes formels. Il n'y a donc pas de
résolution.Par exemple, on peut faire hériter Voiture de Véhicule car une Voiture
est une spécialisation de Véhicule (un cas d'école longtemps
rabaché...:) ). Mais dans le cas des objets mathématiques ;
effectivement ça ne marche plus vraiment.
On peut aussi s'amuser à faire hériter Véhicule de Voiture. Après tout,
un Véhicule comme un Avion, c'est une sorte de voiture avec des ailes,
ou un Véhicule comme un Bateau, c'est une sorte de voiture qui flotte.
Je pense cependant que la relation "Carré hérite de Rectangle" a plus de
sens. Par exemple, si jamais on voulait étendre la classe pour
introduire le calcul d'aire. La relation valable pour le rectangle
A=hauteur * largeur resterait valide pour le carré (A=côté*côté),
tandis qu'une modélisation inverse rendrait nécessaire la redéfinition
de la méthode pour le rectangle. Et, évidemment, c'est le cas pour pas
mal de propriétés du rectangle. Donc, pour garantir la maintenabilité,
je privilégierai ce choix d'héritage.
Tout dépend des pré- et post-conditions que tu choisis. Si tu n'en
écris aucune, n'importe quoi est possible.
Avez-vous des exemples de méthodes qui fonctionnent dans l'autre sens ?
C'est-à-dire, en supposant que Carré hérite de Rectangle, des méthodes
qui sont établies pour le carré et reste valide pour le rectangle par
héritage, alors que dans la modélisation inverse, elle nécessiterait une
redéfinition ?
Par exemple: déplacer, faire_tourner, colorier, etc.