Principes et conseils : Généralisation
Rubriques
De nombreuses choses de la vie réelle ont des propriétés communes. Les chiens et les chats sont des animaux, par exemple. Les objets peuvent également avec des propriétés communes, que vous pouvez clarifier à l'aide de la généralisation entre leurs classes. En extrayant les propriétés communes en classes propres, vous serez capable de changer et d'entretenir le système plus facilement à l'avenir.
Une généralisation montre qu'une classe hérite d'une autre. La classe qui hérite est appelé descendant. La classe qui laisse un héritage est appelée ancêtre.
L'héritage signifie que la définition de l'ancêtre (y compris toutes propriétés comme les attributs, relations, ou opérations sur ses objets) est également valide pour les objets du descendant. La généralisation est tirée de la classe descendant vers sa classe ancêtre.
La généralisation peut intervenir à plusieurs étapes, ce qui vous laisse modeler les hiérarchies d'héritage multi-niveaux complexes. 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éciales plus bas.
Autrement dit, vous pouvez utiliser la généralisation pour modeler les spécialisations d'un concept plus général.
Exemple
Dans le système de machine de recyclage, toutes les classes - Canette, Bouteille et Casier, décrivent des types différents d'éléments déposés. Ils sont deux propriétés en commun, en plus d'être du même type : chacun possède une hauteur et un poids. Vous pouvez modeler ces propriétés à travers des attributs et opérations dans une classe séparée, Elément de dépôt. Canette, Bouteille et Casier hériteront des propriétés de cette classe.

Les classes Canette, Bouteille et Casier ont des propriétés communes, la hauteur et le poids. Chacun est une spécialisation du concept général Elément de dépôt.
Une classe peut hériter de plusieurs autres classes à travers l'héritage multiple, bien que généralement elle n'hérite que d'une seule.
Il existe quelques problèmes potentiels que vous devez connaître si vous utilisez l'héritage multiple :
- Si la classe hérite de plusieurs classes, vous devez vérifier comment les relations, les opérations et les attributs sont nommés dans les ancêtres. Si le même nom apparaît chez plusieurs ancêtres, vous devez décrire ce que cela signifie à la classe spécifique qui hérite, par exemple, en qualifiant le nom pour indiquer sa source de déclaration.
- Si l'héritage répété est utilisé ; dans ce cas, le même ancêtre laisse plusieurs héritages à un descendant. Lorsque cela se produit, la hiérarchie d'héritage aura une forme "losange" comme illustré ci-dessous.

Héritage multiple et répété. La classe Scrolling Window
With Dialog Box hérite plusieurs fois de la classe Window.
Une question peut se poser dans ce contexte : "Combien d'exemplaires des attributs de Window sont inclus dans les instances de Scrolling Window With Dialog Box ?" Alors, si vous utilisez l'héritage répété, vous devez avoir une définition claire de ses sémantiques ; dans la plupart des cas, c'est le langage de programmation prenant en charge l'héritage multiple qui donne la définition.
En général, les règles du langage de programmation qui régissent l'héritage multiple sont complexes, et souvent difficiles à utiliser correctement. Ainsi, utilisez l'héritage multiple uniquement si cela est nécessaire et toujours avec précaution.
Une classe qui n'est pas instanciée et qui n'existe que pour d'autres classes pour leur laisser un héritage est une classe abstraite. Les classes qui sont réellement instanciées sont des classes concrètes. Notez que pour être utile, une classe abstraite doit au moins avoir un descendant.
Exemple
Un Emplacement de palette dans le système de stockage-manutention est une classe abstraite qui représente des propriétés communes à différents types d'emplacements de palette. La classe est héritée par les classes concrètes Station, Transporteur, et Unité de stockage ; toutes pouvant être un emplacement de palette dans le dépôt. Tous ces objets ont une propriété en commun : ils contiennent une ou plusieurs palettes.

La classe héritée, ici emplacement de palette, est abstraite et non instanciées en soi.
Parce que les stéréotypes de classe ont différents objets, l'héritage d'un stéréotype de classe à un autre n'a pas lieu d'être. Laisser une classe limite hériter d'une classe entité, par exemple, transformerait la classe limite en hybride. Par conséquent, vous devez utiliser les généralisations uniquement entre classes du même stéréotype.
Vous pouvez utiliser la généralisation pour exprimer deux relations entre classes :
- "Subtyping", en spécifiant que le descendant est un sous-type de l'ancêtre.
"Subtyping" signifie que le descendant hérite de la structure et du comportement de l'ancêtre, et que le descendant est un type de l'ancêtre (c'est-à-dire, le descendant est un sous-type qui peut remplacer tous ses ancêtres toute situation).
- "Subclassing", en spécifiant que le descendant est une sous-classe (mais pas un sous-type) de l'ancêtre. "Subclassing" signifie que le descendant hérite de la structure et du comportement de l'ancêtre, et que le descendant n'est pas un type de l'ancêtre.
Vous pouvez créer des relations telles que celles-ci en décomposant les propriétés communes à plusieurs classes et en les plaçant dans une classe séparée dont les autres héritent ; ou en créant de nouvelles classes qui spécialisent des classes plus générales et en les laissant hériter des classes générales.
Si les deux variantes coïncident, vous ne devriez avoir aucune difficulté à établir le bon héritage entre les classes. Dans certains cas, cependant, elles ne coïncident pas, et vous devez faire attention à ce que l'utilisation de l'héritage reste compréhensible. Vous devez au minimum connaître l'objet de chaque relation d'héritage dans le modèle.
"Subtyping" signifie que le descendant est un type de l'ancêtre qui peut remplacer tous ses ancêtres toute situation."Subtyping" est un cas spécial de polymorphisme, et constitue une propriété importante parce qu'elle vous laisse concevoir tous les clients (objets qui utilisent l'ancêtre) sans prendre les éventuels descendants de l'ancêtre en considération. Cela rend les objets client plus généraux et réutilisables. Lorsque le
client utilise l'objet réel, il fonctionnera d'une façon spécifique, et il trouvera toujours que l'objet effectue sa tâche. "Subtyping" s'assure que le système tolérera les changements dans l'ensemble de sous-types.
Exemple
Dans un système de stockage-manutention, la classe Interface transporteur définit les fonctions de base pour la communication avec tous les types d'équipements de transport, comme les grues et les camions. La classe définit l'opération
executeTransport, entre autres.

Les classes Interface camion et Interface grue héritent de l'Interface transporteur ; c'est-à-dire, des objets des deux classes répondront au message executeTransport. Les objets peuvent remplacer l'Interface transporteur à tout moment et présenteront tous ses comportements. Ainsi, d'autres objets (objets client) peuvent envoyer un message à un objet Interface transporteur, sans savoir si un objet Interface camion ou Interface grue répondra au message.
La classe Interface transporteur peut même être abstraite, jamais instanciée en soi. Dans ce cas, l'Interface transporteur peut définir uniquement la signature de l'opération executeTransport, alors que les classes descendantes l'implémentent.
Certains langages orientés objet, comme C++, utilisent la hiérarchie des classes comme type de hiérarchie, obligeant le concepteur à utiliser l'héritage pour établir des sous-types dans le modèle de conception. D'autres, comme Smalltalk-80, n'ont aucune vérification de type au temps de compilation. Si les objets ne peuvent pas répondre à un message reçu, ils généreront un message d'erreur.
Ce peut être une bonne idée d'utiliser la généralisation pour indiquer les relations de sous-type même dans des langages sans vérification de type. 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, que le langage le permette ou pas. Le style approprié de cette utilisation de l'héritage dépend largement des conventions du langage de programmation.
"Subclassing" constitue l'aspect de réutilisation de la généralisation. Lorsque vous établissez des sous-classes, vous considérez quelles parties d'une implémentation vous pouvez réutiliser en héritant les propriétés définies par d'autres classes. L'établissement de sous-classe réduit le travail et vous permet de réutiliser le code lors de l'implémentation d'une classe particulière.
Exemple
Dans la bibliothèque de classes Smalltalk-80, le dictionnaire de classes hérite de propriétés de l'Ensemble.

La raison de cette généralisation est que le dictionnaire peut alors réutiliser certaines méthodes générales et stratégies de stockage de l'implémentation de l'Ensemble. Même si un Dictionnaire peut être considéré comme un Ensemble (contenant des paires clé-valeur), le dictionnaire n'est pas un sous-type de l'Ensemble car vous ne pouvez pas ajouter tout type d'objet à un dictionnaire (uniquement des paires clé-valeur). Les objets qui utilisent le Dictionnaire ne savent pas qu'il s'agit en fait d'un Ensemble.
L'établissement de sous-classes mène souvent à des hiérarchies d'héritage illogiques qui sont difficiles à comprendre et entretenir. Par conséquent, il vous est recommandé d'utiliser l'héritage uniquement pour la réutilisation, sauf recommandation contraire dans votre langage de programmation. La maintenance de ce type de réutilisation est généralement assez compliquée. Tout changement dans l'Ensemble de classes peut impliquer de grands changements de toutes les classes héritant de l'Ensemble de classes. Faites attention à cela et ne faites hériter que les classes stables.
L'héritage gèlera en fait l'implémentation de l'Ensemble de classes, car y apporter des changements est trop onéreux.
L'utilisation des relations de généralisation dans la conception doivent dépendre largement des sémantiques et de l'utilisation proposée de l'héritage dans le langage de programmation.
Les langages orientés objet prennent en charge l'héritage entre classes, mais les langages non orientés objet ne le font pas. Vous devez gérer les caractéristiques du langage dans le modèle de conception. Si vous utilisez un langage qui ne prend 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 modeler la simulation dans le modèle de conception et ne pas utiliser des généralisations pour décrire les structures d'héritage.
Modeler les structures d'héritage avec des généralisations, puis simuler l'héritage dans l'implémentation, peut ruiner la conception.
Si vous utilisez un langage qui ne prend 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 modeler la simulation dans le modèle de conception et ne pas utiliser des généralisations pour décrire les structures d'héritage.
Modeler les structures d'héritage avec des généralisations, puis simuler l'héritage uniquement dans l'implémentation peut ruiner la conception.
Vous devrez probablement changer les interfaces et d'autres propriétés d'objets pendant la simulation. Il vous est recommandé de simuler l'héritage de l'une des façons suivantes :
- En laissant le descendant renvoyer des messages à l'ancêtre.
- En dupliquant le code de l'ancêtre dans chaque descendant. Dans ce cas, aucune classe d'ancêtres n'est créée.
Exemple
Dans cet exemple, les descendants renvoient des messages à l'ancêtre via des liens qui sont des instances d'associations.

Le comportement commun aux objets Canette, Bouteille et Casier est attribué à une classe spéciale. Les objets pour lesquels ce comportement est commun envoient un
message à un objet Elément de dépôt pour exécuter le comportement lorsque nécessaire.
|