Il faut les rendre explicites.
Au pire:
class::method(int arg…){
assert(pre_condition(this,arg);
// do method.
assert(post_condition(this,arg);
}
Au mieux, utiliser un langage comme Eiffel ou lisp qui permet d'écrire
et de vérifier et/ou prouver les conditions.
Qu'est ce qu'il y a de contre nature à écrire:
{
char* x;
x="hello";
}
si c'est ce qu'on veut dire?
En pratique, on met pré-condition = Vrai, post-condition = Faux, et on
est tranquille. Mais on ne peut rien prouver sur son programme, et il
peut faire n'importe quoi.
Il faut les rendre explicites.
Au pire:
class::method(int arg…){
assert(pre_condition(this,arg);
// do method.
assert(post_condition(this,arg);
}
Au mieux, utiliser un langage comme Eiffel ou lisp qui permet d'écrire
et de vérifier et/ou prouver les conditions.
Qu'est ce qu'il y a de contre nature à écrire:
{
char* x;
x="hello";
}
si c'est ce qu'on veut dire?
En pratique, on met pré-condition = Vrai, post-condition = Faux, et on
est tranquille. Mais on ne peut rien prouver sur son programme, et il
peut faire n'importe quoi.
Il faut les rendre explicites.
Au pire:
class::method(int arg…){
assert(pre_condition(this,arg);
// do method.
assert(post_condition(this,arg);
}
Au mieux, utiliser un langage comme Eiffel ou lisp qui permet d'écrire
et de vérifier et/ou prouver les conditions.
Qu'est ce qu'il y a de contre nature à écrire:
{
char* x;
x="hello";
}
si c'est ce qu'on veut dire?
En pratique, on met pré-condition = Vrai, post-condition = Faux, et on
est tranquille. Mais on ne peut rien prouver sur son programme, et il
peut faire n'importe quoi.
Le 16/04/2012 12:43, Pascal J. Bourguignon a écrit :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.
Euh... ça me semble un peu tiré par les cheveux, quand même. Je ne vois
pas trop comment un Bateau est censé implémenter toutes les méthodes
applicables de façon "naturelle" à une Voiture comme la méthode rouler.
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.
Comme quoi, ces considérations théoriques ne sont pas forcément
pertinentes. Il vaut mieux s'appuyer sur un cas d'implémentation concret.
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.
Ça ne répond pas vraiment à ma question. Si dans ma modélisation "Carré
hérite de rectangle", je définis déplacer, faire_tourner ou colorier
dans Rectangle je n'aurai pas à les redéfinir dans Carré ; les
transformations géométriques associées, la translation et la rotation,
s'appliqueront très bien.
Le 16/04/2012 12:43, Pascal J. Bourguignon a écrit :
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.
Euh... ça me semble un peu tiré par les cheveux, quand même. Je ne vois
pas trop comment un Bateau est censé implémenter toutes les méthodes
applicables de façon "naturelle" à une Voiture comme la méthode rouler.
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.
Comme quoi, ces considérations théoriques ne sont pas forcément
pertinentes. Il vaut mieux s'appuyer sur un cas d'implémentation concret.
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.
Ça ne répond pas vraiment à ma question. Si dans ma modélisation "Carré
hérite de rectangle", je définis déplacer, faire_tourner ou colorier
dans Rectangle je n'aurai pas à les redéfinir dans Carré ; les
transformations géométriques associées, la translation et la rotation,
s'appliqueront très bien.
Le 16/04/2012 12:43, Pascal J. Bourguignon a écrit :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.
Euh... ça me semble un peu tiré par les cheveux, quand même. Je ne vois
pas trop comment un Bateau est censé implémenter toutes les méthodes
applicables de façon "naturelle" à une Voiture comme la méthode rouler.
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.
Comme quoi, ces considérations théoriques ne sont pas forcément
pertinentes. Il vaut mieux s'appuyer sur un cas d'implémentation concret.
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.
Ça ne répond pas vraiment à ma question. Si dans ma modélisation "Carré
hérite de rectangle", je définis déplacer, faire_tourner ou colorier
dans Rectangle je n'aurai pas à les redéfinir dans Carré ; les
transformations géométriques associées, la translation et la rotation,
s'appliqueront très bien.
Laurent Georget writes:Le 16/04/2012 12:43, Pascal J. Bourguignon a écrit :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.
Euh... ça me semble un peu tiré par les cheveux, quand même. Je ne vois
pas trop comment un Bateau est censé implémenter toutes les méthodes
applicables de façon "naturelle" à une Voiture comme la méthode rouler.
C'est justement la question! Qu'est ce que ça signifie que "rouler"?
La sémantique est donnée/spécifiée par les pre- et post-conditions.
Si on établi dans les pré- et post-conditions que rouler consiste à se
déplacer parallèlement à une surface à peu près plane et à peu près
horizontale, (pourquoi irait-on spécifier quoi que ce soit d'autre sur
cette surface, comme sa solidité ou sa matière?), alors un bateau
"roule" aussi sur l'eau. (Et un avion "roule" en l'air en général, je
n'ai rien spécifié sur la distance entre la "Voiture" et la surface).
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.
Comme quoi, ces considérations théoriques ne sont pas forcément
pertinentes. Il vaut mieux s'appuyer sur un cas d'implémentation concret.
Mais c'est tout à fait concrêt. Je n'ai encore jamais vu de code avec
toutes ses pré- et post-conditions. En théorie à la fac, oui. J'ai
aussi ouï-dire que ça existait par exemple à la NASA, mais en pratique
je n'en ai jamais vu, donc en pratique les pré-conditions sont Vrai et
les post-conditions sont Faux.
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.
Ça ne répond pas vraiment à ma question. Si dans ma modélisation "Carré
hérite de rectangle", je définis déplacer, faire_tourner ou colorier
dans Rectangle je n'aurai pas à les redéfinir dans Carré ; les
transformations géométriques associées, la translation et la rotation,
s'appliqueront très bien.
Exact. Je n'en trouve pas.
Laurent Georget <laurent@lgeorget.eu> writes:
Le 16/04/2012 12:43, Pascal J. Bourguignon a écrit :
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.
Euh... ça me semble un peu tiré par les cheveux, quand même. Je ne vois
pas trop comment un Bateau est censé implémenter toutes les méthodes
applicables de façon "naturelle" à une Voiture comme la méthode rouler.
C'est justement la question! Qu'est ce que ça signifie que "rouler"?
La sémantique est donnée/spécifiée par les pre- et post-conditions.
Si on établi dans les pré- et post-conditions que rouler consiste à se
déplacer parallèlement à une surface à peu près plane et à peu près
horizontale, (pourquoi irait-on spécifier quoi que ce soit d'autre sur
cette surface, comme sa solidité ou sa matière?), alors un bateau
"roule" aussi sur l'eau. (Et un avion "roule" en l'air en général, je
n'ai rien spécifié sur la distance entre la "Voiture" et la surface).
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.
Comme quoi, ces considérations théoriques ne sont pas forcément
pertinentes. Il vaut mieux s'appuyer sur un cas d'implémentation concret.
Mais c'est tout à fait concrêt. Je n'ai encore jamais vu de code avec
toutes ses pré- et post-conditions. En théorie à la fac, oui. J'ai
aussi ouï-dire que ça existait par exemple à la NASA, mais en pratique
je n'en ai jamais vu, donc en pratique les pré-conditions sont Vrai et
les post-conditions sont Faux.
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.
Ça ne répond pas vraiment à ma question. Si dans ma modélisation "Carré
hérite de rectangle", je définis déplacer, faire_tourner ou colorier
dans Rectangle je n'aurai pas à les redéfinir dans Carré ; les
transformations géométriques associées, la translation et la rotation,
s'appliqueront très bien.
Exact. Je n'en trouve pas.
Laurent Georget writes:Le 16/04/2012 12:43, Pascal J. Bourguignon a écrit :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.
Euh... ça me semble un peu tiré par les cheveux, quand même. Je ne vois
pas trop comment un Bateau est censé implémenter toutes les méthodes
applicables de façon "naturelle" à une Voiture comme la méthode rouler.
C'est justement la question! Qu'est ce que ça signifie que "rouler"?
La sémantique est donnée/spécifiée par les pre- et post-conditions.
Si on établi dans les pré- et post-conditions que rouler consiste à se
déplacer parallèlement à une surface à peu près plane et à peu près
horizontale, (pourquoi irait-on spécifier quoi que ce soit d'autre sur
cette surface, comme sa solidité ou sa matière?), alors un bateau
"roule" aussi sur l'eau. (Et un avion "roule" en l'air en général, je
n'ai rien spécifié sur la distance entre la "Voiture" et la surface).
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.
Comme quoi, ces considérations théoriques ne sont pas forcément
pertinentes. Il vaut mieux s'appuyer sur un cas d'implémentation concret.
Mais c'est tout à fait concrêt. Je n'ai encore jamais vu de code avec
toutes ses pré- et post-conditions. En théorie à la fac, oui. J'ai
aussi ouï-dire que ça existait par exemple à la NASA, mais en pratique
je n'en ai jamais vu, donc en pratique les pré-conditions sont Vrai et
les post-conditions sont Faux.
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.
Ça ne répond pas vraiment à ma question. Si dans ma modélisation "Carré
hérite de rectangle", je définis déplacer, faire_tourner ou colorier
dans Rectangle je n'aurai pas à les redéfinir dans Carré ; les
transformations géométriques associées, la translation et la rotation,
s'appliqueront très bien.
Exact. Je n'en trouve pas.
"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.
On Mon, 16 Apr 2012 00:45:33 +0200,
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 ?
On Mon, 16 Apr 2012 00:45:33 +0200,
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 ?
On Mon, 16 Apr 2012 00:45:33 +0200,
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 ?
Je n'ai jamais trop réfléchi en terme de pré/post mais plutôt
d'invariant (qui serait donc toujours vrai à n'importe qu'elle moment de
la vie de l'instance et pour n'importe qu'elle méthode).
Je vois ici plutôt les pré conditions comme une validation des arguments
uniquement. Les post-conditions étant des invariants sur lesquelles
l'utilisateur d'une classe peut valider l'usage qu'il en fait.
Donc la difficulté, il me semble vient de trouver les pré/post
conditions adéquates, celles dépendant de l'usage qu'on fait de ces
classes. Je crois que dans l'article, il parle qu'on ne peut connaître
la validité d'une hiérarchie de manière isolée.
Si j'ajoute des post-conditions aux exemples donnés.
le principe de substitution ne fonctionne plus.
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.
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...
Je n'ai jamais trop réfléchi en terme de pré/post mais plutôt
d'invariant (qui serait donc toujours vrai à n'importe qu'elle moment de
la vie de l'instance et pour n'importe qu'elle méthode).
Je vois ici plutôt les pré conditions comme une validation des arguments
uniquement. Les post-conditions étant des invariants sur lesquelles
l'utilisateur d'une classe peut valider l'usage qu'il en fait.
Donc la difficulté, il me semble vient de trouver les pré/post
conditions adéquates, celles dépendant de l'usage qu'on fait de ces
classes. Je crois que dans l'article, il parle qu'on ne peut connaître
la validité d'une hiérarchie de manière isolée.
Si j'ajoute des post-conditions aux exemples donnés.
le principe de substitution ne fonctionne plus.
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.
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...
Je n'ai jamais trop réfléchi en terme de pré/post mais plutôt
d'invariant (qui serait donc toujours vrai à n'importe qu'elle moment de
la vie de l'instance et pour n'importe qu'elle méthode).
Je vois ici plutôt les pré conditions comme une validation des arguments
uniquement. Les post-conditions étant des invariants sur lesquelles
l'utilisateur d'une classe peut valider l'usage qu'il en fait.
Donc la difficulté, il me semble vient de trouver les pré/post
conditions adéquates, celles dépendant de l'usage qu'on fait de ces
classes. Je crois que dans l'article, il parle qu'on ne peut connaître
la validité d'une hiérarchie de manière isolée.
Si j'ajoute des post-conditions aux exemples donnés.
le principe de substitution ne fonctionne plus.
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.
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...
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 ?
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 ?
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 ?
Le 15-04-2012, Marc a écrit :"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 rafiner un peu en disant qu'en C++, on n'utilise souvent
pas assez les classes abstraites, et en heritant de classes
concrètres, on récupère simultanément
1) un sous-type
2) des attributs et une implantation par défaut.
Cela correspond aux besoins courants, mais pas à tous les besoins.
Il manque, à ma connaissance, la possibilité de n'hériter que de
l'interface d'une classe concrète. Il faut redécouper la classe
dont on veut hériter en classe abstraite/concrète, et aller
modifier le code utilisateur déjà fait.
Le 15-04-2012, Marc <marc.glisse@gmail.com> a écrit :
"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 rafiner un peu en disant qu'en C++, on n'utilise souvent
pas assez les classes abstraites, et en heritant de classes
concrètres, on récupère simultanément
1) un sous-type
2) des attributs et une implantation par défaut.
Cela correspond aux besoins courants, mais pas à tous les besoins.
Il manque, à ma connaissance, la possibilité de n'hériter que de
l'interface d'une classe concrète. Il faut redécouper la classe
dont on veut hériter en classe abstraite/concrète, et aller
modifier le code utilisateur déjà fait.
Le 15-04-2012, Marc a écrit :"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 rafiner un peu en disant qu'en C++, on n'utilise souvent
pas assez les classes abstraites, et en heritant de classes
concrètres, on récupère simultanément
1) un sous-type
2) des attributs et une implantation par défaut.
Cela correspond aux besoins courants, mais pas à tous les besoins.
Il manque, à ma connaissance, la possibilité de n'hériter que de
l'interface d'une classe concrète. Il faut redécouper la classe
dont on veut hériter en classe abstraite/concrète, et aller
modifier le code utilisateur déjà fait.
In article <jn35ti$98k$,
Marc Boyer wrote:On peut rafiner un peu en disant qu'en C++, on n'utilise souvent
pas assez les classes abstraites, et en heritant de classes
concrètres, on récupère simultanément
1) un sous-type
2) des attributs et une implantation par défaut.
Cela correspond aux besoins courants, mais pas à tous les besoins.
Il manque, à ma connaissance, la possibilité de n'hériter que de
l'interface d'une classe concrète. Il faut redécouper la classe
dont on veut hériter en classe abstraite/concrète, et aller
modifier le code utilisateur déjà fait.
C'est peut-etre les pratiques anciennes, mais il y a quand meme pas
mal de gens qui evangelisent les bonnes pratiques, comme Sutter ou Meyers.
Il faut decouper les classes en trucs gerables. Le principe 1 classe,
1 responsabilite, s'applique particulierement bien en C++.
Le gros interet d'avoir un langage avec une abstraction penalty tres faible
(voire inexistante si on sort l'artillerie lourde et qu'on inline
judicieusement) c'est qu'on peut ecrire les choses de maniere tres
detaillee sans avoir a payer de prix au runtime...
Les gens qui codent des grosses classes viennent souvent d'univers ou
ecrire de toutes petites classes coutent tres cher, parce qu'on empile 15
abstractions l'une sur l'autre...
Ne pas hesiter a leur faire lire a peu pres tout Sutter... ;-)
In article <jn35ti$98k$1@news.cict.fr>,
Marc Boyer <Marc.Boyer@cert.onera.fr.invalid> wrote:
On peut rafiner un peu en disant qu'en C++, on n'utilise souvent
pas assez les classes abstraites, et en heritant de classes
concrètres, on récupère simultanément
1) un sous-type
2) des attributs et une implantation par défaut.
Cela correspond aux besoins courants, mais pas à tous les besoins.
Il manque, à ma connaissance, la possibilité de n'hériter que de
l'interface d'une classe concrète. Il faut redécouper la classe
dont on veut hériter en classe abstraite/concrète, et aller
modifier le code utilisateur déjà fait.
C'est peut-etre les pratiques anciennes, mais il y a quand meme pas
mal de gens qui evangelisent les bonnes pratiques, comme Sutter ou Meyers.
Il faut decouper les classes en trucs gerables. Le principe 1 classe,
1 responsabilite, s'applique particulierement bien en C++.
Le gros interet d'avoir un langage avec une abstraction penalty tres faible
(voire inexistante si on sort l'artillerie lourde et qu'on inline
judicieusement) c'est qu'on peut ecrire les choses de maniere tres
detaillee sans avoir a payer de prix au runtime...
Les gens qui codent des grosses classes viennent souvent d'univers ou
ecrire de toutes petites classes coutent tres cher, parce qu'on empile 15
abstractions l'une sur l'autre...
Ne pas hesiter a leur faire lire a peu pres tout Sutter... ;-)
In article <jn35ti$98k$,
Marc Boyer wrote:On peut rafiner un peu en disant qu'en C++, on n'utilise souvent
pas assez les classes abstraites, et en heritant de classes
concrètres, on récupère simultanément
1) un sous-type
2) des attributs et une implantation par défaut.
Cela correspond aux besoins courants, mais pas à tous les besoins.
Il manque, à ma connaissance, la possibilité de n'hériter que de
l'interface d'une classe concrète. Il faut redécouper la classe
dont on veut hériter en classe abstraite/concrète, et aller
modifier le code utilisateur déjà fait.
C'est peut-etre les pratiques anciennes, mais il y a quand meme pas
mal de gens qui evangelisent les bonnes pratiques, comme Sutter ou Meyers.
Il faut decouper les classes en trucs gerables. Le principe 1 classe,
1 responsabilite, s'applique particulierement bien en C++.
Le gros interet d'avoir un langage avec une abstraction penalty tres faible
(voire inexistante si on sort l'artillerie lourde et qu'on inline
judicieusement) c'est qu'on peut ecrire les choses de maniere tres
detaillee sans avoir a payer de prix au runtime...
Les gens qui codent des grosses classes viennent souvent d'univers ou
ecrire de toutes petites classes coutent tres cher, parce qu'on empile 15
abstractions l'une sur l'autre...
Ne pas hesiter a leur faire lire a peu pres tout Sutter... ;-)
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.
Marc Boyer <Marc.Boyer@cert.onera.fr.invalid> 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.
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.