(snip)
BTW, j'en profite pour recommander la BonnePratique(tm) suivante : ne
pas coder le nom de la classe en dur dans le code. Christophe n'aurait
pas ce problème si scale() avait été implémenté ainsi:
def scale(self, scale):
return self.__class__(self.x*scale, self.y*scale, self.z*scale)
Facile à dire.
(snip)
BTW, j'en profite pour recommander la BonnePratique(tm) suivante : ne
pas coder le nom de la classe en dur dans le code. Christophe n'aurait
pas ce problème si scale() avait été implémenté ainsi:
def scale(self, scale):
return self.__class__(self.x*scale, self.y*scale, self.z*scale)
Facile à dire.
(snip)
BTW, j'en profite pour recommander la BonnePratique(tm) suivante : ne
pas coder le nom de la classe en dur dans le code. Christophe n'aurait
pas ce problème si scale() avait été implémenté ainsi:
def scale(self, scale):
return self.__class__(self.x*scale, self.y*scale, self.z*scale)
Facile à dire.
Christophe wrote:
(snip)
BTW, j'en profite pour recommander la BonnePratique(tm) suivante : ne
pas coder le nom de la classe en dur dans le code. Christophe n'aurait
pas ce problème si scale() avait été implémenté ainsi:
def scale(self, scale):
return self.__class__(self.x*scale, self.y*scale, self.z*scale)
Facile à dire.
Facile à faire aussi dans *tes* développements !-)
Ce n'était pas une solution à ton problème, mais une pratique qui, si
elle avait été suivie par l'auteur de ta classe Vecteur, t'aurais évité
le problème que tu a maintenant à résoudre.
Christophe wrote:
(snip)
BTW, j'en profite pour recommander la BonnePratique(tm) suivante : ne
pas coder le nom de la classe en dur dans le code. Christophe n'aurait
pas ce problème si scale() avait été implémenté ainsi:
def scale(self, scale):
return self.__class__(self.x*scale, self.y*scale, self.z*scale)
Facile à dire.
Facile à faire aussi dans *tes* développements !-)
Ce n'était pas une solution à ton problème, mais une pratique qui, si
elle avait été suivie par l'auteur de ta classe Vecteur, t'aurais évité
le problème que tu a maintenant à résoudre.
Christophe wrote:
(snip)
BTW, j'en profite pour recommander la BonnePratique(tm) suivante : ne
pas coder le nom de la classe en dur dans le code. Christophe n'aurait
pas ce problème si scale() avait été implémenté ainsi:
def scale(self, scale):
return self.__class__(self.x*scale, self.y*scale, self.z*scale)
Facile à dire.
Facile à faire aussi dans *tes* développements !-)
Ce n'était pas une solution à ton problème, mais une pratique qui, si
elle avait été suivie par l'auteur de ta classe Vecteur, t'aurais évité
le problème que tu a maintenant à résoudre.
bruno at modulix wrote:Christophe wrote:
(snip)
BTW, j'en profite pour recommander la BonnePratique(tm) suivante : ne
pas coder le nom de la classe en dur dans le code. Christophe
n'auraitpas ce problème si scale() avait été implémenté ainsi:
def scale(self, scale):
return self.__class__(self.x*scale, self.y*scale, self.z*scale)
Facile à dire.
Facile à faire aussi dans *tes* développements !-)
Ce n'était pas une solution à ton problème, mais une pratique qui, si
elle avait été suivie par l'auteur de ta classe Vecteur, t'aurais évité
le problème que tu a maintenant à résoudre.
Oui mais c'est pas évident de penser à ça.
En conclusion quand même, on peut noter que ce genre de situation est moins
bloquante en Python que dans la pluspart des langages objets. Il y a
plusieurs solutions possibles qui ne nécessitent pas trop d'entretient
( modifier directement Vector, encapsuler Vector et faire un passthrough
avec __getattr__ et __setattr__. Il reste quand même le problème du code
externe qui peut retourner des nouveaux Vecteur au lieu de MonVecteur.
Tiens, je viens de penser à une autre 3eme solution à ce problème. Remplacer
Vecteur dans son module par la nouvelle classe directement. Un truc du
genre :
import Vecteur
Vecteur.Vecteur = MonVecteur
Cela marche très bien sauf pour 1 cas : les modules qui font "from Vecteur
import Vecteur". Dans ce cas il faut bien s'arranger pour faire le
remplacement avec que ces module soit importé.
PS : moi j'ai pas de problème avec ça,
bruno at modulix wrote:
Christophe wrote:
(snip)
BTW, j'en profite pour recommander la BonnePratique(tm) suivante : ne
pas coder le nom de la classe en dur dans le code. Christophe
n'aurait
pas ce problème si scale() avait été implémenté ainsi:
def scale(self, scale):
return self.__class__(self.x*scale, self.y*scale, self.z*scale)
Facile à dire.
Facile à faire aussi dans *tes* développements !-)
Ce n'était pas une solution à ton problème, mais une pratique qui, si
elle avait été suivie par l'auteur de ta classe Vecteur, t'aurais évité
le problème que tu a maintenant à résoudre.
Oui mais c'est pas évident de penser à ça.
En conclusion quand même, on peut noter que ce genre de situation est moins
bloquante en Python que dans la pluspart des langages objets. Il y a
plusieurs solutions possibles qui ne nécessitent pas trop d'entretient
( modifier directement Vector, encapsuler Vector et faire un passthrough
avec __getattr__ et __setattr__. Il reste quand même le problème du code
externe qui peut retourner des nouveaux Vecteur au lieu de MonVecteur.
Tiens, je viens de penser à une autre 3eme solution à ce problème. Remplacer
Vecteur dans son module par la nouvelle classe directement. Un truc du
genre :
import Vecteur
Vecteur.Vecteur = MonVecteur
Cela marche très bien sauf pour 1 cas : les modules qui font "from Vecteur
import Vecteur". Dans ce cas il faut bien s'arranger pour faire le
remplacement avec que ces module soit importé.
PS : moi j'ai pas de problème avec ça,
bruno at modulix wrote:Christophe wrote:
(snip)
BTW, j'en profite pour recommander la BonnePratique(tm) suivante : ne
pas coder le nom de la classe en dur dans le code. Christophe
n'auraitpas ce problème si scale() avait été implémenté ainsi:
def scale(self, scale):
return self.__class__(self.x*scale, self.y*scale, self.z*scale)
Facile à dire.
Facile à faire aussi dans *tes* développements !-)
Ce n'était pas une solution à ton problème, mais une pratique qui, si
elle avait été suivie par l'auteur de ta classe Vecteur, t'aurais évité
le problème que tu a maintenant à résoudre.
Oui mais c'est pas évident de penser à ça.
En conclusion quand même, on peut noter que ce genre de situation est moins
bloquante en Python que dans la pluspart des langages objets. Il y a
plusieurs solutions possibles qui ne nécessitent pas trop d'entretient
( modifier directement Vector, encapsuler Vector et faire un passthrough
avec __getattr__ et __setattr__. Il reste quand même le problème du code
externe qui peut retourner des nouveaux Vecteur au lieu de MonVecteur.
Tiens, je viens de penser à une autre 3eme solution à ce problème. Remplacer
Vecteur dans son module par la nouvelle classe directement. Un truc du
genre :
import Vecteur
Vecteur.Vecteur = MonVecteur
Cela marche très bien sauf pour 1 cas : les modules qui font "from Vecteur
import Vecteur". Dans ce cas il faut bien s'arranger pour faire le
remplacement avec que ces module soit importé.
PS : moi j'ai pas de problème avec ça,
BTW, j'en profite pour recommander la BonnePratique(tm) suivante : ne
pas coder le nom de la classe en dur dans le code. Christophe n'aurait
pas ce problème si scale() avait été implémenté ainsi:
def scale(self, scale):
return self.__class__(self.x*scale, self.y*scale, self.z*scale)
BTW, j'en profite pour recommander la BonnePratique(tm) suivante : ne
pas coder le nom de la classe en dur dans le code. Christophe n'aurait
pas ce problème si scale() avait été implémenté ainsi:
def scale(self, scale):
return self.__class__(self.x*scale, self.y*scale, self.z*scale)
BTW, j'en profite pour recommander la BonnePratique(tm) suivante : ne
pas coder le nom de la classe en dur dans le code. Christophe n'aurait
pas ce problème si scale() avait été implémenté ainsi:
def scale(self, scale):
return self.__class__(self.x*scale, self.y*scale, self.z*scale)
BTW, j'en profite pour recommander la BonnePratique(tm) suivante : ne
pas coder le nom de la classe en dur dans le code. Christophe n'aurait
pas ce problème si scale() avait été implémenté ainsi:
def scale(self, scale):
return self.__class__(self.x*scale, self.y*scale, self.z*scale)
Pas tout à fait d'accord: une classe dérivée peut avoir un autre
constructeur, qui n'est pas compatible, du genre NormalizedVector(rho,
phi).
Effectivement. Mais dans ce cas, est-il judicieux d'utiliser l'héritage
BTW, j'en profite pour recommander la BonnePratique(tm) suivante : ne
pas coder le nom de la classe en dur dans le code. Christophe n'aurait
pas ce problème si scale() avait été implémenté ainsi:
def scale(self, scale):
return self.__class__(self.x*scale, self.y*scale, self.z*scale)
Pas tout à fait d'accord: une classe dérivée peut avoir un autre
constructeur, qui n'est pas compatible, du genre NormalizedVector(rho,
phi).
Effectivement. Mais dans ce cas, est-il judicieux d'utiliser l'héritage
BTW, j'en profite pour recommander la BonnePratique(tm) suivante : ne
pas coder le nom de la classe en dur dans le code. Christophe n'aurait
pas ce problème si scale() avait été implémenté ainsi:
def scale(self, scale):
return self.__class__(self.x*scale, self.y*scale, self.z*scale)
Pas tout à fait d'accord: une classe dérivée peut avoir un autre
constructeur, qui n'est pas compatible, du genre NormalizedVector(rho,
phi).
Effectivement. Mais dans ce cas, est-il judicieux d'utiliser l'héritage
amaury wrote:BTW, j'en profite pour recommander la BonnePratique(tm) suivante : ne
pas coder le nom de la classe en dur dans le code. Christophe n'aurait
pas ce problème si scale() avait été implémenté ainsi:
def scale(self, scale):
return self.__class__(self.x*scale, self.y*scale, self.z*scale)
Pas tout à fait d'accord: une classe dérivée peut avoir un autre
constructeur, qui n'est pas compatible, du genre NormalizedVector(rho,
phi).
Effectivement. Mais dans ce cas, est-il judicieux d'utiliser l'héritage
? C'est une réutilisation partielle d'implémentation, sans sémantique de
sous-typage, donc la délégation serait peut-être plus appropriée ?
amaury wrote:
BTW, j'en profite pour recommander la BonnePratique(tm) suivante : ne
pas coder le nom de la classe en dur dans le code. Christophe n'aurait
pas ce problème si scale() avait été implémenté ainsi:
def scale(self, scale):
return self.__class__(self.x*scale, self.y*scale, self.z*scale)
Pas tout à fait d'accord: une classe dérivée peut avoir un autre
constructeur, qui n'est pas compatible, du genre NormalizedVector(rho,
phi).
Effectivement. Mais dans ce cas, est-il judicieux d'utiliser l'héritage
? C'est une réutilisation partielle d'implémentation, sans sémantique de
sous-typage, donc la délégation serait peut-être plus appropriée ?
amaury wrote:BTW, j'en profite pour recommander la BonnePratique(tm) suivante : ne
pas coder le nom de la classe en dur dans le code. Christophe n'aurait
pas ce problème si scale() avait été implémenté ainsi:
def scale(self, scale):
return self.__class__(self.x*scale, self.y*scale, self.z*scale)
Pas tout à fait d'accord: une classe dérivée peut avoir un autre
constructeur, qui n'est pas compatible, du genre NormalizedVector(rho,
phi).
Effectivement. Mais dans ce cas, est-il judicieux d'utiliser l'héritage
? C'est une réutilisation partielle d'implémentation, sans sémantique de
sous-typage, donc la délégation serait peut-être plus appropriée ?
On Tue, 29 Nov 2005 19:12:12 +0100, bruno at modulix
wrote:amaury wrote:BTW, j'en profite pour recommander la BonnePratique(tm) suivante : ne
pas coder le nom de la classe en dur dans le code. Christophe n'aurait
pas ce problème si scale() avait été implémenté ainsi:
def scale(self, scale):
return self.__class__(self.x*scale, self.y*scale, self.z*scale)
Pas tout à fait d'accord: une classe dérivée peut avoir un autre
constructeur, qui n'est pas compatible, du genre NormalizedVector(rho,
phi).
Effectivement. Mais dans ce cas, est-il judicieux d'utiliser l'héritage
? C'est une réutilisation partielle d'implémentation, sans sémantique de
sous-typage, donc la délégation serait peut-être plus appropriée ?
Je n'ai pas l'impression que le fait d'avoir des constructeurs avec des
signatures différentes veuille forcément dire qu'on n'a pas de
sémantique de sous-typage.
Si on prend l'exemple tarte à la crème de la
classe Rectangle qui spécialise la classe Polygone,
les paramètres du
constructeur vont très certainement être différents: on sent bien une
liste de points pour celui de Polygone, et des coordonnées + une largeur
et une hauteur pour Rectangle.
Ca paraît assez naturel de faire comme
ça,
mais ça rend l'utilisation de self.__class__ pour créer un objet
impossible dans Polygone. Bien sûr, on pourrait accepter les paramètres
de constructeur de Polygone dans Rectangle, vérifier qu'ils représentent
bien un rectangle et lever une exception si ce n'est pas le cas, mais ça
paraît bien lourdingue...
Et pourtant, Rectangle est bien une
spécialisation de Polygone
(ou alors, c'est que tous les bouquins nous
mentent depuis qu'on est tout petit...).
C'est vrai que d'avoir des signatures de méthodes qui changent entre une
super-classe et une sous-classe est fréquemment le signe que la
hiérarchie de classes est mal partie.
Mais pour moi, les constructeurs
ont toujours été plus ou moins exclus de cette règle: une sous-classe
introduit en général des contraintes en plus,
donc pouvoir construire un
objet de la sous-classe avec les mêmes infos que les objets de la
super-classe peut très bien ne pas être approprié.
Donc le truc de créer
des instances avec self.__class__ me paraît pour le moins risqué...
Mais bon, moi, ce que j'en dis...
On Tue, 29 Nov 2005 19:12:12 +0100, bruno at modulix <onurb@xiludom.gro>
wrote:
amaury wrote:
BTW, j'en profite pour recommander la BonnePratique(tm) suivante : ne
pas coder le nom de la classe en dur dans le code. Christophe n'aurait
pas ce problème si scale() avait été implémenté ainsi:
def scale(self, scale):
return self.__class__(self.x*scale, self.y*scale, self.z*scale)
Pas tout à fait d'accord: une classe dérivée peut avoir un autre
constructeur, qui n'est pas compatible, du genre NormalizedVector(rho,
phi).
Effectivement. Mais dans ce cas, est-il judicieux d'utiliser l'héritage
? C'est une réutilisation partielle d'implémentation, sans sémantique de
sous-typage, donc la délégation serait peut-être plus appropriée ?
Je n'ai pas l'impression que le fait d'avoir des constructeurs avec des
signatures différentes veuille forcément dire qu'on n'a pas de
sémantique de sous-typage.
Si on prend l'exemple tarte à la crème de la
classe Rectangle qui spécialise la classe Polygone,
les paramètres du
constructeur vont très certainement être différents: on sent bien une
liste de points pour celui de Polygone, et des coordonnées + une largeur
et une hauteur pour Rectangle.
Ca paraît assez naturel de faire comme
ça,
mais ça rend l'utilisation de self.__class__ pour créer un objet
impossible dans Polygone. Bien sûr, on pourrait accepter les paramètres
de constructeur de Polygone dans Rectangle, vérifier qu'ils représentent
bien un rectangle et lever une exception si ce n'est pas le cas, mais ça
paraît bien lourdingue...
Et pourtant, Rectangle est bien une
spécialisation de Polygone
(ou alors, c'est que tous les bouquins nous
mentent depuis qu'on est tout petit...).
C'est vrai que d'avoir des signatures de méthodes qui changent entre une
super-classe et une sous-classe est fréquemment le signe que la
hiérarchie de classes est mal partie.
Mais pour moi, les constructeurs
ont toujours été plus ou moins exclus de cette règle: une sous-classe
introduit en général des contraintes en plus,
donc pouvoir construire un
objet de la sous-classe avec les mêmes infos que les objets de la
super-classe peut très bien ne pas être approprié.
Donc le truc de créer
des instances avec self.__class__ me paraît pour le moins risqué...
Mais bon, moi, ce que j'en dis...
On Tue, 29 Nov 2005 19:12:12 +0100, bruno at modulix
wrote:amaury wrote:BTW, j'en profite pour recommander la BonnePratique(tm) suivante : ne
pas coder le nom de la classe en dur dans le code. Christophe n'aurait
pas ce problème si scale() avait été implémenté ainsi:
def scale(self, scale):
return self.__class__(self.x*scale, self.y*scale, self.z*scale)
Pas tout à fait d'accord: une classe dérivée peut avoir un autre
constructeur, qui n'est pas compatible, du genre NormalizedVector(rho,
phi).
Effectivement. Mais dans ce cas, est-il judicieux d'utiliser l'héritage
? C'est une réutilisation partielle d'implémentation, sans sémantique de
sous-typage, donc la délégation serait peut-être plus appropriée ?
Je n'ai pas l'impression que le fait d'avoir des constructeurs avec des
signatures différentes veuille forcément dire qu'on n'a pas de
sémantique de sous-typage.
Si on prend l'exemple tarte à la crème de la
classe Rectangle qui spécialise la classe Polygone,
les paramètres du
constructeur vont très certainement être différents: on sent bien une
liste de points pour celui de Polygone, et des coordonnées + une largeur
et une hauteur pour Rectangle.
Ca paraît assez naturel de faire comme
ça,
mais ça rend l'utilisation de self.__class__ pour créer un objet
impossible dans Polygone. Bien sûr, on pourrait accepter les paramètres
de constructeur de Polygone dans Rectangle, vérifier qu'ils représentent
bien un rectangle et lever une exception si ce n'est pas le cas, mais ça
paraît bien lourdingue...
Et pourtant, Rectangle est bien une
spécialisation de Polygone
(ou alors, c'est que tous les bouquins nous
mentent depuis qu'on est tout petit...).
C'est vrai que d'avoir des signatures de méthodes qui changent entre une
super-classe et une sous-classe est fréquemment le signe que la
hiérarchie de classes est mal partie.
Mais pour moi, les constructeurs
ont toujours été plus ou moins exclus de cette règle: une sous-classe
introduit en général des contraintes en plus,
donc pouvoir construire un
objet de la sous-classe avec les mêmes infos que les objets de la
super-classe peut très bien ne pas être approprié.
Donc le truc de créer
des instances avec self.__class__ me paraît pour le moins risqué...
Mais bon, moi, ce que j'en dis...
Et pourtant, Rectangle est bien une
spécialisation de Polygone
En math, oui. Pas en informatique.(ou alors, c'est que tous les bouquins nous
mentent depuis qu'on est tout petit...).
Les bouquins de math, non. Quant aux bouquins d'informatique à deux
balles qui donnent ça comme exemple de cas d'utilisation de l'héritage,
la décence m'interdis d'expliquer ici comment en faire bon usage :(
[...]
La plupart des intros à l'OO mettent beaucoup trop en valeur l'héritage,
qui n'est à l'origine qu'une facilité, et n'est en rien nécessaire -
sauf bien sûr quand il sert également de support pour le polymorphisme.
Et pourtant, Rectangle est bien une
spécialisation de Polygone
En math, oui. Pas en informatique.
(ou alors, c'est que tous les bouquins nous
mentent depuis qu'on est tout petit...).
Les bouquins de math, non. Quant aux bouquins d'informatique à deux
balles qui donnent ça comme exemple de cas d'utilisation de l'héritage,
la décence m'interdis d'expliquer ici comment en faire bon usage :(
[...]
La plupart des intros à l'OO mettent beaucoup trop en valeur l'héritage,
qui n'est à l'origine qu'une facilité, et n'est en rien nécessaire -
sauf bien sûr quand il sert également de support pour le polymorphisme.
Et pourtant, Rectangle est bien une
spécialisation de Polygone
En math, oui. Pas en informatique.(ou alors, c'est que tous les bouquins nous
mentent depuis qu'on est tout petit...).
Les bouquins de math, non. Quant aux bouquins d'informatique à deux
balles qui donnent ça comme exemple de cas d'utilisation de l'héritage,
la décence m'interdis d'expliquer ici comment en faire bon usage :(
[...]
La plupart des intros à l'OO mettent beaucoup trop en valeur l'héritage,
qui n'est à l'origine qu'une facilité, et n'est en rien nécessaire -
sauf bien sûr quand il sert également de support pour le polymorphisme.
Eric Brunel wrote:Je n'ai pas l'impression que le fait d'avoir des constructeurs avec des
signatures différentes veuille forcément dire qu'on n'a pas de
sémantique de sous-typage.
Ca dépend de la définition de "sous-typage" !-)Si on prend l'exemple tarte à la crème de la
classe Rectangle qui spécialise la classe Polygone,
Et vlan ! Non seulement c'est une tarte à la crème, mais c'est aussi une
grossière erreur de conception - la preuve :les paramètres du
constructeur vont très certainement être différents: on sent bien une
liste de points pour celui de Polygone, et des coordonnées + une largeur
et une hauteur pour Rectangle.
Le fait que mathématiquement, un rectangle soit "une sorte de" polygone
n'implique pas que la classe (informatique) représentant un rectangle
soit une sous-classe (au sens de l'héritage d'implémentation) de la
classe Polygone. Au contraire, comme tu le démontre, ces deux classes
présentent des divergences manifestes. Il convient donc de déterminer ce
qu'elles ont de commun de ce qui les distingue fondamentalement.
BTW, il y avait un excellent article sur ce sujet, concernant les
rectangles et les carrés, mais je n'ai plus l'URL (si quelqu'un la
retrouve..).
L'idée principale était que, si on utilise soit les contrats (le
résultat serait sensiblement le même avec les test unitaires je pense),
on se rendait vite compte que même si un carré est un rectangle, Carré
ne pouvait pas être une sous-classe de Rectangle. En effet, modifier la
largeur d'un rectangle est supposé ne pas modifier sa hauteur, alors que
modifier la largeur d'un carré implique de modifier sa hauteur. En fait,
un carré n'a ni hauteur ni largeur, il a un côté.
(ou alors, c'est que tous les bouquins nous
mentent depuis qu'on est tout petit...).
Les bouquins de math, non. Quant aux bouquins d'informatique à deux
balles qui donnent ça comme exemple de cas d'utilisation de l'héritage,
la décence m'interdis d'expliquer ici comment en faire bon usage :(
En bref, si ta sous-classe n'est pas compatible avec la classe de base,
ce n'est pas un sous-type de la classe de base (selon la définition de
Liskov, qui n'est bien sûr pas la seule possible ou valide), et donc il
vaut mieux utiliser dans ce cas un autre mécanisme que l'héritage.
Eric Brunel wrote:
Je n'ai pas l'impression que le fait d'avoir des constructeurs avec des
signatures différentes veuille forcément dire qu'on n'a pas de
sémantique de sous-typage.
Ca dépend de la définition de "sous-typage" !-)
Si on prend l'exemple tarte à la crème de la
classe Rectangle qui spécialise la classe Polygone,
Et vlan ! Non seulement c'est une tarte à la crème, mais c'est aussi une
grossière erreur de conception - la preuve :
les paramètres du
constructeur vont très certainement être différents: on sent bien une
liste de points pour celui de Polygone, et des coordonnées + une largeur
et une hauteur pour Rectangle.
Le fait que mathématiquement, un rectangle soit "une sorte de" polygone
n'implique pas que la classe (informatique) représentant un rectangle
soit une sous-classe (au sens de l'héritage d'implémentation) de la
classe Polygone. Au contraire, comme tu le démontre, ces deux classes
présentent des divergences manifestes. Il convient donc de déterminer ce
qu'elles ont de commun de ce qui les distingue fondamentalement.
BTW, il y avait un excellent article sur ce sujet, concernant les
rectangles et les carrés, mais je n'ai plus l'URL (si quelqu'un la
retrouve..).
L'idée principale était que, si on utilise soit les contrats (le
résultat serait sensiblement le même avec les test unitaires je pense),
on se rendait vite compte que même si un carré est un rectangle, Carré
ne pouvait pas être une sous-classe de Rectangle. En effet, modifier la
largeur d'un rectangle est supposé ne pas modifier sa hauteur, alors que
modifier la largeur d'un carré implique de modifier sa hauteur. En fait,
un carré n'a ni hauteur ni largeur, il a un côté.
(ou alors, c'est que tous les bouquins nous
mentent depuis qu'on est tout petit...).
Les bouquins de math, non. Quant aux bouquins d'informatique à deux
balles qui donnent ça comme exemple de cas d'utilisation de l'héritage,
la décence m'interdis d'expliquer ici comment en faire bon usage :(
En bref, si ta sous-classe n'est pas compatible avec la classe de base,
ce n'est pas un sous-type de la classe de base (selon la définition de
Liskov, qui n'est bien sûr pas la seule possible ou valide), et donc il
vaut mieux utiliser dans ce cas un autre mécanisme que l'héritage.
Eric Brunel wrote:Je n'ai pas l'impression que le fait d'avoir des constructeurs avec des
signatures différentes veuille forcément dire qu'on n'a pas de
sémantique de sous-typage.
Ca dépend de la définition de "sous-typage" !-)Si on prend l'exemple tarte à la crème de la
classe Rectangle qui spécialise la classe Polygone,
Et vlan ! Non seulement c'est une tarte à la crème, mais c'est aussi une
grossière erreur de conception - la preuve :les paramètres du
constructeur vont très certainement être différents: on sent bien une
liste de points pour celui de Polygone, et des coordonnées + une largeur
et une hauteur pour Rectangle.
Le fait que mathématiquement, un rectangle soit "une sorte de" polygone
n'implique pas que la classe (informatique) représentant un rectangle
soit une sous-classe (au sens de l'héritage d'implémentation) de la
classe Polygone. Au contraire, comme tu le démontre, ces deux classes
présentent des divergences manifestes. Il convient donc de déterminer ce
qu'elles ont de commun de ce qui les distingue fondamentalement.
BTW, il y avait un excellent article sur ce sujet, concernant les
rectangles et les carrés, mais je n'ai plus l'URL (si quelqu'un la
retrouve..).
L'idée principale était que, si on utilise soit les contrats (le
résultat serait sensiblement le même avec les test unitaires je pense),
on se rendait vite compte que même si un carré est un rectangle, Carré
ne pouvait pas être une sous-classe de Rectangle. En effet, modifier la
largeur d'un rectangle est supposé ne pas modifier sa hauteur, alors que
modifier la largeur d'un carré implique de modifier sa hauteur. En fait,
un carré n'a ni hauteur ni largeur, il a un côté.
(ou alors, c'est que tous les bouquins nous
mentent depuis qu'on est tout petit...).
Les bouquins de math, non. Quant aux bouquins d'informatique à deux
balles qui donnent ça comme exemple de cas d'utilisation de l'héritage,
la décence m'interdis d'expliquer ici comment en faire bon usage :(
En bref, si ta sous-classe n'est pas compatible avec la classe de base,
ce n'est pas un sous-type de la classe de base (selon la définition de
Liskov, qui n'est bien sûr pas la seule possible ou valide), et donc il
vaut mieux utiliser dans ce cas un autre mécanisme que l'héritage.
de retomber sur le problème classique des effets de bord.
de retomber sur le problème classique des effets de bord.
de retomber sur le problème classique des effets de bord.