OVH Cloud OVH Cloud

Heritage multiple vs. composition

22 réponses
Avatar
Olivier Croquette
Bonjour,

je voudrais votre avis sur une utilisation de l'héritage multiple qui ne
parait pas courante.

Soit une classe véhicule, dont l'utilisateur doit pouvoir déterminer la
position et à la couleur.

Soit une implémentation basée sur l'héritage multiple:

class Color {
public:
SetColor(...);
protected:
double r,g,b,a;
}

class Position {
public:
SetPosition(...);
protected:
double x,y;
}


class Vehicule : public Position, public Color {
...
}

Note: les classes Vehicule, Position, Color sont spécifiées et
implémentées par les mêmes développeurs (ie. pas d'héritage multiple de
classes "externes").


En gros, l'héritage multiple est utilisé pour ajouter en même temps une
interface et une implémentation à une classe.


Les avantages de cette implémentation sont les suivants:

- syntaxe très simple au niveau de Vehicule pour rajouter de nouvelles
propriétés

- très peu d'effort au niveau de Vehicule pour rajouter de nouvelles
propriétés

- Vehicule hérite automatiquement de toutes amélioration à Position
Par exemple, si SetPositionX() et SetPositionY() sont implémentées
dans Position(), Vehicule en profite automatiquement.

- syntaxe très simple pour l'utilisateur, qui n'a qu'à qu'à faire
monvehicule.SetColor()

Une limitation (au moins) est qu'on est embêté si le véhicule doit un
jour devenir bicolore.

A part ça, l'héritage multiple étant souvent sujet à controverse, y
a-t'il un risque particulier à une telle implémentation?
Quels sont les précautions à prendre?

10 réponses

1 2 3
Avatar
Benoît Bréholée
Olivier Croquette wrote:

Merci pour vos commentaires!

Je me doutais bien que beaucoup déconseilleraient ce design, mais
jusqu'à maintenant, je n'ai pas encore vu de description des problèmes
*précis* auxquels cela expose.

De mon côté, j'y vois le gros avantage de simplifier énormément les
écritures et de limiter la quantité de code trivial, genre:

void Vehicule::SetPosition(x,y) { this->position.Set(x,y); }

ou bien:

Position &Vehicule::GetPositionRef() { return this->position; }


et ce pour chaque attribut.


Seulement pour ceux pour lesquels c'est pertinent, ce qui idéalement
devrait faire beaucoup moins. Par exemple, si le véhicule a sa position
comme attribut, on pourrait considérer que c'est lui qui va la mettre à
jour par des fonctions membres de plus haut niveau ("avancer sur le
chemin défini") et qu'aucun autre objet ne doit accéder directement en
écriture aux attributs x/y. De même en lecture, il faut définir quelle
est la partie visible de l'état de l'objet.

Ces choix dépendent de la modélisation que l'on fait, du problème, et
évidemment en pratique, on va souvent choisir de faire des set/get sur
des attributs. Mais ces fonctions membres set/get doivent être des choix
de conception, elles ne devraient pas être ajoutées systématiquement dès
lors qu'on ajoute un nouvel attribut...

Avatar
Loïc Joly

Juste un élément supplémentaire pour aller dans le même sens... Comment
ferais-tu dans la version 2 du logiciel où l'on te demande de gérer les
voitures bicolores ?

--
Loïc
Avatar
Hamiral
Loïc Joly wrote:


Juste un élément supplémentaire pour aller dans le même sens... Comment
ferais-tu dans la version 2 du logiciel où l'on te demande de gérer les
voitures bicolores ?



class VehiculeBicolore : public Vehicule {
protected:
Color DeuxiemeCouleur;

// etc.
}

:)

--
Hamiral

Avatar
kanze
Fabien LE LEZ wrote:
On Sat, 12 Aug 2006 13:26:22 +0200, Olivier Croquette :

class Position {
public:
SetPosition(...);
protected:
double x,y;
}


J'oubliais les deux autres problèmes, qui sont sans doute des
fautes de frappe :
- il manque le point-virgule à la fin ;
- il manque le destructeur virtuel.


La manque du destructeur virtuel est plus probablement le
symptome du vrai problème : de toute probabilité,
« Position » serait un type de valeur, avec supporte pour la
copie, l'affectation, et tout qui va avec. Et donc, mal indiqué
comme classe de base. Et comme tu disais dans ta première
réponse, un Vehicule *a* une Position, il n'est pas une
Position.

D'ailleurs, le posteur original a présenté lui-même le plus fort
argument contre cette utilisation d'héritage : Vehicule hérite
automatiquement de tout changement dans l'interface de Position.
C'est exactement le contraire de ce qu'on veut. Que l'interface
de Position change, il faut bien modifier tous les utilisateurs
de Position. Avec l'héritage, il faudrait aussi modifier tous
les utilisateurs de Vehicule.

Selon le cas, ta solution de ObjetAvecPosition pourrait être
envisagée. Mais franchement, au moins d'avoir plusieurs
types différents de ObjetAvecPosition, je ne vois pas de grand
intérêt.

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34


Avatar
kanze
Olivier Croquette wrote:

Je me doutais bien que beaucoup déconseilleraient ce design,
mais jusqu'à maintenant, je n'ai pas encore vu de description
des problèmes *précis* auxquels cela expose.


Tu ne considères pas le fait qu'on peut utiliser un objet de
type Vehicule partout où on démande une position un problème
précis ? Penses-y un moment :

Position* v1 = new Vehicule( ... ) ;
Position* v2 = new Vehicule( ... ) ;

*v1 = *v2 ;

Que signifie cette dernière ligne ? Ou quelque chose du
genre :

Position p( *v1 ) ;

De mon côté, j'y vois le gros avantage de simplifier
énormément les écritures


En augmentant énormement les risques d'erreurs d'utilisation et
les difficultés de compréhension. Tu écris le code une fois ;
tu l'utilises des milliers de fois. Tout ce que tu gagnes à
l'écriture, tu vas le perdre lors de l'utilisation.

et de limiter la quantité de code trivial, genre:

void Vehicule::SetPosition(x,y) { this->position.Set(x,y); }

ou bien:

Position &Vehicule::GetPositionRef() { return this->position; }

et ce pour chaque attribut.


Si ce sont vraiement que des attributs, pûrs et simples, et que
la conception garantie qu'ils le restent, tu pourrais bien
déclarer les membres publics. Mais le cas me semble assez
rare : dès qu'une classe a un comportement, on se rétrouve avec
des invariantes entre les différents composants de son état. Et
du coup, il ne faut pas permettre qu'on les changent sans
certaines vérifications.

Sinon, il existe bien des classes dont le rôle, c'est simplement
de collectionner un ensemble de valeurs arbitraire. Dans ces
cas-là, je les déclare avec struct, et je rends tous les membres
publiques.

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

Avatar
kanze
Sylvain wrote:
Fabien LE LEZ wrote on 12/08/2006 13:37:

1- Une position n'a pas de position.


au primaire on apprend "être" et "avoir"; ici une position est
un état.


Justement, c'est une valeur. Quelque chose qu'« a » une
voiture, non quelque chose qu'une voiture « est ».

Ici, Fabien s'opposait sans doute à la fonction
« setPosition » dans position. Les setters habituellement
modifient quelques attributs de l'objet dont ils sont membre, et
logiquement, Position n'est pas un attribut de Position,
puisqu'une Position est une Position, et non a une Position.

Si tu écris
Position ma_position;
ma_position.SetPosition (...);
tu écris "la position de la position ma_position est ...".
Ça n'a pas de sens.


alors que dans "Position point;" 'point' a bien une position;


Tu confonds instance et type. Si point est une Position, il n'a
pas de Position ; il est une Position. (Mais c'est peut-être un
mauvais nom de variable pour une Position.) Le nom de la
variable n'influe pas sur son type.

voulais-tu seulement dire qu'il peut toujours exister une
(mauvaise) façon de présenter qui permet de balayer le fond en
raillant la forme ?


Il voulait sans doute dire que le code en question viole toute
règle de bonne conception, et mène à des difficultés de
compréhension et des erreurs dans l'utilisation.

2- Ton héritage indique qu'un véhicule *est* une position.
Là non plus, ça n'a pas de sens.


tu disais "a une position", tu te contredis juste pour
contredire le PO?


Est-ce que par hazard tu as lu ce qu'il a écrit ? Un Vehicule
n'est pas une Position, il a une Position. Et que l'héritage
public sert, à peu d'exceptions près, à implémenter un rapport
« estUn ». Une Position est une Position, n'a donc pas de
Position (sinon, il y a un problème de récursion), et que
SetPostion n'y a pas de sens. Un Vehicule n'est pas une
Position, et donc, l'héritage public ne convient pas.

une véhicule a bien une position, non ?


Et c'est ce qu'il a dit.

En fait, pour régler ces deux problèmes, il suffit de
changer le nom de la classe, et de l'appeler
"ObjetAyantUnePosition".


ou encore
"ClasseQuiPeutEtreEtenduParQuelqueChoseQuiPeutEtreOuAvoirUnePosition",
mais bon c'est un peu long ...

3- Des variables membres presque publiques, puisqu'une autre
classe peut y accéder. Tôt ou tard, ça te causera des
problèmes. D'une manière générale, toutes les variables
membres doivent être privées.


d'une manière générale, il faut se méfier des généralités ;)


Comme il a dit, tôt ou tard, ça poserait des problèmes.
Probablement, à juger d'après les noms des classes : j'ai bien
des objets dans mon code où tous les variables sont publiques.
(Pas beaucoup, c'est vrai.)

La visibilité en est aussi un critère : plus la visibilité est
large, plus il y a de risque avec les variables publiques. Dans
une classe qui sert de façade d'un sous-système, la moindre
variable publique est une cause d'alarme. Dans une classe
privée embriquée, en revanche, on peut supposer une utilisation
limitée à la classe extérieur, est les variables publiques ne
posent pas de problème a priori.

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34


Avatar
Olivier Croquette
kanze wrote:
Tu ne considères pas le fait qu'on peut utiliser un objet de
type Vehicule partout où on démande une position un problème
précis ? Penses-y un moment :

Position* v1 = new Vehicule( ... ) ;
Position* v2 = new Vehicule( ... ) ;

*v1 = *v2 ;

Que signifie cette dernière ligne ? Ou quelque chose du
genre :

Position p( *v1 ) ;


Heu... honnêtement, que les gens écrivent des trucs qui n'ont pas de
sens en utilisant mes classes, ce n'est pas mon soucis majeur.

Le principal est qu'il y ait une manière sensée de les utiliser.


En augmentant énormement les risques d'erreurs d'utilisation et
les difficultés de compréhension. Tu écris le code une fois ;
tu l'utilises des milliers de fois. Tout ce que tu gagnes à
l'écriture, tu vas le perdre lors de l'utilisation.


C'est justement là le truc: au moins pendant la phase de développement,
j'ai tout un tas d'attributs (dans le genre de la position) que je
change régulièrement, et qui sont utilisés par plusieurs types d'objets.
Et je change régulièrement l'interface pour changer ces attributs.

Pour rester sur le même exemple, si j'ai besoin demain de pouvoir
utiliser des unités pour la position, j'ai juste à changer l'interface
de Position (ou ObjectAyantUnePosition...) et tout le monde en profite
automatiquement.

Sans utiliser ce design, je vois 3 possibilités:

- mettre la position en attribut publique. Comme tu le dis, c'est loin
d'être la panacée

- Position &Vehicule::GetPositionRef() { return this->position; }
Ce qui est quasiment pareil que le définir publique, la lisibilité en
moins.

- redéfinir toute l'interface de Vehicule qui permet de manipuler la
position pour prendre en compte les unités, mais aussi les interfaces de
toutes mes classes ayant une position


J'ai l'impression que l'argument principal contre ce design est:
"c'est pas comme d'habitude, donc c'est plus sujet à confusion et/ou
erreur".
Ce qui n'est pas faux, mais ce n'est pas non plus objectivement un
problème technique.

Avatar
Fabien LE LEZ
On Wed, 16 Aug 2006 22:00:08 +0200, Olivier Croquette
:

Pour rester sur le même exemple, si j'ai besoin demain de pouvoir
utiliser des unités pour la position, j'ai juste à changer l'interface
de Position (ou ObjectAyantUnePosition...) et tout le monde en profite
automatiquement.

Sans utiliser ce design, je vois 3 possibilités:

- mettre la position en attribut publique. Comme tu le dis, c'est loin
d'être la panacée


Ce n'est pas loin d'être la même chose que l'héritage public.



Mais la question est surtout : peux-tu changer la position d'un
véhicule indépendamment de toutes les autres variables membres ?


Si oui, une variable membre publique (ou avec un accesseur "direct"
comme ce que tu proposes ici :
- Position &Vehicule::GetPositionRef() { return this->position; }


) n'est pas forcément une mauvaise solution.

Mais il serait peut-être encore mieux de "sortir" carrément la
variable "position" de la classe :

class Vehicule
{
/// pas de position
};

struct VehiculeAvecPosition
{
Vehicule vehicule;
Position position;
};


Sinon, aucune des solutions que tu proposes ne fonctionne.

La solution la plus simple est de créer un "Get()" simple, et un
"Set()" plus complexe, qui modifie la position et ajuste les autres
variables en conséquence.

Avatar
kanze
Olivier Croquette wrote:
kanze wrote:
Tu ne considères pas le fait qu'on peut utiliser un objet de
type Vehicule partout où on démande une position un problème
précis ? Penses-y un moment :

Position* v1 = new Vehicule( ... ) ;
Position* v2 = new Vehicule( ... ) ;

*v1 = *v2 ;

Que signifie cette dernière ligne ? Ou quelque chose du
genre :

Position p( *v1 ) ;


Heu... honnêtement, que les gens écrivent des trucs qui n'ont
pas de sens en utilisant mes classes, ce n'est pas mon soucis
majeur.


Mais tu l'encourage. Quand tu en dérives publiquement, tu dis
que ça marche. (Il y a des exceptions, si par exemple la classe
de base est un trait. Mais ceci n'en est pas une.)

Le principal est qu'il y ait une manière sensée de les
utiliser.


Le problème, c'est qu'avec ta solution, tu suggères très
fortement que ce qui n'est pas sensé l'est.

En augmentant énormement les risques d'erreurs d'utilisation
et les difficultés de compréhension. Tu écris le code une
fois ; tu l'utilises des milliers de fois. Tout ce que tu
gagnes à l'écriture, tu vas le perdre lors de l'utilisation.


C'est justement là le truc: au moins pendant la phase de
développement, j'ai tout un tas d'attributs (dans le genre de
la position) que je change régulièrement, et qui sont utilisés
par plusieurs types d'objets. Et je change régulièrement
l'interface pour changer ces attributs.


J'ai une idée, alors. Tu commences par faire un peu de
conception.

Évidemment, si tu es en train d'expérimenter, pour toi-même,
disons pour te renseigner sur ce qui est raisonablement
possible, avant de faire la conception, il n'y a pas de
problème. Tu fais comme ça t'arrange, parce que tu n'as pas de
clients qui en dépend, et le code serait jeté par la suite.
C'est une autre contexte ; dans de tels cas, moi aussi, j'ai
tout genre d'atrocités que je ne laisserais jamais dans un code
de production.

Si, en revanche, tu as des clients, ou qu'il faut maintenir le
code, tu dois bien commencer par une conception, qui définit
l'interface de l'objet, et cette interface ne doit pas bouger
beaucoup, parce que chaque fois que tu le changes, il faut
toucher tout le code client.

Pour rester sur le même exemple, si j'ai besoin demain de
pouvoir utiliser des unités pour la position, j'ai juste à
changer l'interface de Position (ou ObjectAyantUnePosition...)
et tout le monde en profite automatiquement.


Tu as un drôle d'idée de « profiter ». Tu veux dire que tous
les clients soient automatiquement obligés à modifier leur code.

Je crois que notre différence d'avis se trouve là. La
dérivation, surtout publique, créer un couplage très fort. Tu
sembles estîmer ce couplage positivement. Tandis que toutes mes
expériences me fait croire qu'il faut systèmatiquement limiter
les couplages au strict nécessaire.

Sans utiliser ce design, je vois 3 possibilités:

- mettre la position en attribut publique. Comme tu le dis,
c'est loin d'être la panacée


Dériver publiquement ou avoir un attribut publique, c'est à peu
près la même chose.

- Position &Vehicule::GetPositionRef() { return this->position; }
Ce qui est quasiment pareil que le définir publique, la lisibilité en
moins.


Tout à fait.

- redéfinir toute l'interface de Vehicule qui permet de
manipuler la position pour prendre en compte les unités, mais
aussi les interfaces de toutes mes classes ayant une position


En fait, c'est la quatrième possibilité qui est généralement
celle qu'on préfère : modifier l'implémentation de Vehicule (et
de toutes les autres classes qui utilise Position) de façon à
masquer la modification à leurs clients, afin que la nécessité
des modifications ne propage pas à travers tout le système.

J'ai l'impression que l'argument principal contre ce design
est:
"c'est pas comme d'habitude, donc c'est plus sujet à confusion
et/ou erreur".


Et qu'il introduit un couplage fort entre deux classes. Et qu'il
augmente le charge de maintenance des clients de tes classes.

Ce qui n'est pas faux, mais ce n'est pas non plus
objectivement un problème technique.


Si par technique, tu veux dire que ça empèche que le système
fonctionne, non, ce n'est pas un problème technique. Si tu tiens
compte des coûts de développement et de l'évolution, en
revanche, ta solution augment les coûts d'une façon notable.

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34


Avatar
Alain Gaillard
Dériver publiquement ou avoir un attribut publique, c'est à peu
près la même chose.


- Position &Vehicule::GetPositionRef() { return this->position; }
Ce qui est quasiment pareil que le définir publique, la lisibilité en
moins.



Tout à fait.





Houlà...
A moins que je n'ai perdu le fil en cours de route, ce n'est pas à peu
près la même chose.
Un attribut publique tout le monde peut y accéder même en écriture. Un
attribut privé reste privé même en dérivation publique, sinon ce n'est
même pas la peine de faire de la programmation objet ;)

Même dans une phase "d'esssai de conception" (si je comprends bien la
discussion) des attributs publiques ça ne devrait pas exister AMHA.

--
Alain


1 2 3