Dans la vraie vie, beaucoup de choses ont des propriétés en commun. Un chien et un chat sont, par exemple, tous deux
des animaux. Les propriétés peuvent aussi avoir des propriétés en commun ; vous pouvez les clarifier en utilisant une
généralisation entre leurs classes. En plaçant les propriétés communes dans des classes à part entière, vous pourrez
changer et entretenir votre système plus facilement dans le futur.
Une généralisation montre qu'une classe hérite d'une autre. La classe qui hérite est appelée un enfant. La classe dont
elle hérite est appelée un parent. L'héritage signifie que la définition du parent - incluant les propriétés comme les
attributs, les relations ou les opérations sur ses objets - est aussi valide pour les objets de l'enfant. La
généralisation est établie de la classe enfant vers la classe parent.
La généralisation peut avoir lieu en plusieurs étapes, ce qui vous permet de modéliser des hiérarchies d'héritage à
plusieurs niveaux. Les propriétés générales sont placées dans la partie supérieure de la hiérarchie d'héritage et les
propriétés spécifiques dans la partie inférieure. En d'autres mots, vous pouvez utiliser la généralisation pour
modéliser les spécialisations d'un concept plus général.
Exemple
Dans le système d'une machine de recyclage, toutes les classes - canette, bouteille et caisse - décrivent différents
types d'articles consignés. Ils ont deux propriétés en commun, en dehors du fait d'être du même type : chacun a une
taille et un poids. Vous pouvez modéliser ces propriétés en utilisant des attributs et des opérations dans une classe
séparée article consigné. Les canettes, les bouteilles et les caisses hériteront des propriétés de cette classe.
Les classes canette, bouteille et caisse ont les propriétés taille et poids en commun. Chacune est une spécialisation
du concept général article consigné.
Une classe peut hériter de plusieurs autres classes grâce à l'héritage multiple, bien que généralement elle n'hérite
que d'une.
Si vous utilisez les héritages multiples, voici quelques problèmes potentiels que vous devez connaître :
-
Si la classe hérite de plusieurs classes, vous devez vérifier comment les relations, les opérations et les
attributs sont nommés chez les parents. Si le même nom apparaît chez plusieurs acteurs, vous devez décrire ce que
cela signifie à la classe qui hérite, par exemple en qualifiant le nom pour indiquer sa source de déclaration.
-
Avec l'héritage répété, un enfant hérite plusieurs fois d'un parent. Lorsque cela arrive, la hiérarchie d'héritage
aura une forme de losange, comme illustré ci-dessous.
Héritage multiple et répété. La classe Fenêtre défilante avec boîte de dialogue a hérité de la classe fenêtre plusieurs
fois.
Une question pouvant être soulevée dans ce contexte est "Combien de copies des attributs de la Fenêtre sont incluses
dans les instances de la Fenêtre défilante avec boîte de dialogue ?". Si vous utilisez l'héritage répété, vous devez
donc avoir une définition claire de sa sémantique ; dans la plupart des cas, cela est défini par le langage de
programmation prenant en charge l'héritage multiple.
Généralement, les règles du langage de programmation gérant les héritages multiples sont complexes et souvent difficile
à utiliser correctement. Utilisez donc les héritages multiples seulement lorsque c'est nécessaire et toujours avec
prudence.
Une classe qui n'est pas instanciée et qui n'existe que dans le but que les autres classes héritent d'elles est une
classe abstraite. Une classe instanciée est une classe concrète. Remarquez qu'une classe abstraite doit avoir au moins
un enfant pour être utile.
Exemple
Un emplacement de palette dans le système de gestion de dépôt est une classe entité abstraite qui représente les
propriétés communes aux différents types d'emplacements de palettes. Les classes concrètes poste, transporteur et unité
de stockage, qui peuvent toutes être des emplacements de palette dans le dépôt, héritent de cette classe. Tous ces
objets ont une propriété en commun : ils peuvent tous contenir une ou plusieurs palettes.
La classe héritée, ici l'emplacement de la palette, est abstraite et n'est pas instanciée seule.
Les stéréotypes de classes ayant des buts différents, l'héritage entre stéréotypes de classe n'a aucun sens. Faire
hériter une classe frontière d'une classe entité, par exemple, ferait de la classe frontière une sorte d'hybride. Il
faut donc utiliser les généralisations uniquement entre les classes du même stéréotype.
Vous pouvez utiliser la généralisation pour exprimer deux relations entre des classes :
-
Le sous-typage, qui définit que l'enfant est le sous-type du parent. Le sous-typage signifie que l'enfant hérite de
la structure et du comportement du parent et que l'enfant est du même type que le parent (cela signifie que
l'enfant est un sous-type qui peut remplacer tous ses parents dans toutes les situations).
-
Le sous-classement, qui définit que l'enfant est une sous-classe (mais pas un sous-type) du parent. Le
sous-classement signifie que l'enfant hérite de la structure et du comportement du parent et qu'il n'est pas du
même type que le parent.
Vous pouvez créer ce genre de relations en séparant les propriétés communes à plusieurs classes et en les plaçant dans
des classes séparées dont les autres héritent. Vous pouvez aussi créer de nouvelles classes gérant les plus généraux en
les laissant hériter des classes générales.
Si les deux variantes coïncident, vous n'aurez aucune difficulté à configurer le bon héritage entre les classes.
Parfois, cependant, les variantes ne coïncident pas, dans ce cas vous devez faire attention à ce que l'utilisation de
l'héritage reste compréhensible. Vous devez au moins connaître le but de chaque relation d'héritage présente dans le
modèle.
Le sous-typage signifie que l'enfant est un sous-type qui peut remplacer tous ses parents dans toutes les situations.
Le sous-typage est un cas spécial du polymorphisme. C'est une propriété importante parce qu'elle permet de
conceptualiser tous les clients (les objets qui utilisent les parents) sans tenir compte des enfants potentiels des
parents. Cela rend les objets du client plus communs et réutilisables. Lorsque le client utilisera l'objet réel, il
fonctionnera toujours de la même façon et l'objet effectuera toujours sa tâche. Le sous-typage assure que le système
tolérera les changements dans l'ensemble des sous-types.
Exemple
Dans un système de gestion de dépôt, la classe Interface transporteur définit les fonctionnalités de base pour la
communication avec tous les types d'équipement de transport comme les grues et les camions. La classe définit, entre
autres choses, l'opération Exécuter le transport.
Les interfaces des classes Camion et Grue héritent de l'interface transporteur. Cela signifie que les objets des deux
classes répondront au message Exécuter le transport. Les objets peuvent remplacer l'interface Transporteur à n'importe
quel moment et exécuteront son comportement. Les autres objets (les objets client) peuvent donc envoyer un message à un
objet de l'interface Transporteur, sans savoir si l'interface Camion ou l'interface Grue répondra au message.
La classe interface Transporteur peut même être abstraite, jamais instanciée seule. Dans ce cas, l'interface
Transporteur peut seulement définir seulement la signature de l'opération Exécuter le transport tandis que les classes
enfant l'implémentent.
Certains langages orientés objet, comme C++, utilisent la hiérarchie de classe comme une hiérarchie de type, obligeant
le concepteur à utiliser l'héritage pour effectuer des sous-typages dans le modèle de conception. D'autres langages,
comme Smalltalk-80, n'effectuent pas de contrôle de typage pendant le temps de compilation. Si les objets ne peuvent
pas répondre à un message reçu, ils généreront un message d'erreur.
Utiliser la généralisation pour indiquer les relations de sous-type, même pour les langages sans contrôle de typage,
peut être une bonne idée. Dans certains cas vous devriez utiliser la généralisation pour rendre le modèle d'objet et le
code source plus faciles à comprendre et à entretenir, sans tenir compte du fait que le langage l' autorise ou pas.
L'intérêt de cette utilisation de l'héritage dépend beaucoup des conventions du langage de programmation.
Le sous-classement est l'aspect "réutilisation" de la généralisation. Lorsque vous définissez des sous-classes, vous
choisissez les parties de l'implémentation que vous pouvez réutiliser en héritant de propriétés définies par d'autres
classes. Le sous-classement allège la charge de travail et vous permet de réutiliser le code lors de l'implémentation
d'une classe spécifique.
Exemple
Dans la bibliothèque de classe de Smalltalk-80, la classe Dictionnaire hérite des propriétés de l'ensemble.
Cette généralisation permet au dictionnaire de réutiliser certaines méthodes générales et certaines stratégies de
stockage de l'implémentation de l'ensemble. Même si un dictionnaire peut être vu comme un ensemble (contenant des
paires de valeur clé), ce n'est pas un sous-type de l'ensemble car on ne peut pas ajouter n'importe quelle sorte
d'objet à un dictionnaire, mais seulement des paires de valeurs clés. Les objets qui utilisent le dictionnaire ne
savent pas que c'est un ensemble.
Le sous-classement crée souvent des hiérarchies d'héritage illogiques, difficiles à comprendre et à entretenir. Il
n'est donc pas recommandé d'utiliser l'héritage seulement pour la réutilisation, à moins qu'autre chose ne soit
recommandée pour l'utilisation de votre langage de programmation. La maintenance de ce genre de réutilisation est
généralement assez délicate. Tout changement dans la classe Ensemble implique des grands changements pour toutes les
classes héritant de la classe Ensemble. Gardez cela à l'esprit et n'héritez que des classes stables. L'héritage gèlera
l'implémentation de la classe Ensemble parce qu'y apporter des changements est trop coûteux.
Le fait d'utiliser des relations de généralisation lors de la conception doit largement dépendre de la sémantique et
des utilisations de l'héritage proposées par le langage de programmation. Les langages orientés objet, contrairement
aux langages non-orientés objet, prennent en charge l'héritage entre classes. Il vaut mieux gérer les caractéristiques
des langages dans le modèle de conception. Si vous utilisez un langage ne prenant pas en charge l'héritage ou
l'héritage multiple, vous devez simuler l'héritage dans l'implémentation. Dans ce cas, il vaut mieux modéliser la
simulation dans le modèle de conception et ne pas utiliser les généralisations pour décrire les structures d'héritage.
Modéliser des structures d'héritage avec les généralisations puis simuler l'héritage dans l'implémentation peut gâcher
l'implémentation.
Si vous utilisez un langage ne prenant pas en charge l'héritage ou l'héritage multiple, vous devez simuler l'héritage
dans l'implémentation. Dans ce cas, il vaut mieux modéliser la simulation dans le modèle de conception et ne pas
utiliser les généralisations pour décrire les structures d'héritage. Modéliser des structures d'héritage avec les
généralisations puis simuler l'héritage dans l'implémentation peut gâcher l'implémentation.
Vous devrez probablement changer les interfaces et les autres propriétés d'objet pendant la simulation. Il est
recommandé de simuler l'héritage d'une des façons suivantes :
-
En laissant l'enfant envoyer des messages au parent.
-
En dupliquant le code du parent chez chaque enfant. Dans ce cas, il n'y a pas de classe parent créée.
Exemple
Dans cet exemple, les enfants réacheminent des messages aux parents via des liens qui sont des instances
d'associations.
Une classe spéciale est attribuée aux comportements communs aux Canettes, aux Bouteilles et aux Caisses. Les objets
partageant ce comportement envoient un message à l'objet Article consigné pour exécuter le comportement quand il le
faut.
|