Comment savoir privilégier la composition à la généralisation est toujours le bon choix?

9

Qu'un objet existe ou non physiquement, nous pouvons choisir de le modéliser de différentes manières. Nous pourrions utiliser arbitrairement la généralisation ou la composition dans de nombreux cas. Cependant, le principe du GoF de «privilégier la composition à la généralisation [sic]» nous guide dans l'utilisation de la composition. Ainsi, lorsque nous modélisons, par exemple, une ligne, nous créons une classe qui contient deux membres PointA et PointB de type Point (composition) au lieu d'étendre Point (généralisation). Ceci est juste un exemple simplifié de la façon dont nous pouvons choisir arbitrairement la composition ou l'héritage à modéliser, bien que les objets soient généralement beaucoup plus complexes.

Comment savons-nous que c'est le bon choix? C'est important au moins parce qu'il pourrait y avoir une tonne de refactoring à faire si c'est mal?

CarneyCode
la source
9
Votre exemple ne fonctionne pas vraiment car vous ne pouvez pas dire qu'une ligne est un point, et par conséquent, elle échoue au principe de substitution Liskov et l'héritage n'est pas approprié.
Dean Harding le
@Dean: Comme vous le savez probablement, l'exemple n'est pas central à pointer. Pour mémoire, une ligne peut être représentée comme une équation définie par deux points.
CarneyCode
1
@Songo: Il semble que vous ayez totalement raté le point.
CarneyCode
N'est-ce pas étendre la spécialisation au lieu de la généralisation?
Tulains Córdova

Réponses:

17

Ce n'est pas toujours le bon choix. C'est la plus favorable dans la plupart des cas. Lorsqu'un modèle composite nécessite un changement ou une extension, il est considérablement plus résistant à cela car vous pouvez modifier la composition sans craindre d'affecter d'autres classes par inadvertance.

Comment savons-nous cela? De l'expérience et de l'expérience des autres.

J'ai vu de nombreuses situations où une hiérarchie de classes massive s'est développée à partir de ce qui était à l'origine un concept simple, et c'est là que votre refactoring majeur devient nécessaire. Pendant ce temps, je n'ai jamais vu de situation où une structure de composition devait être refactorisée en héritage.

Mais mes preuves anecdotiques ne devraient pas suffire. Il suffit de regarder autour d'Internet et un bon nombre de personnes ont vécu les mêmes expériences.

pdr
la source
Je l'aime; prendre un vote positif.
CarneyCode
+1 pour "Je n'ai jamais vu de situation où une structure de composition devait être refactorisée en héritage."
Kazark
7

Si vous voulez une règle empirique plus forte au-delà de "privilégier la composition à l'héritage", je pourrais suggérer quelque chose comme ceci:

Parmi les deux façons de spécialiser un objet - l'héritage et la composition - vous devez utiliser l'héritage uniquement lorsque vous avez besoin que votre objet soit polymorphe (soit substituable) pour la classe de base que vous spécialisez.

Cependant, comme toutes les règles de base, une fois que vous comprenez la règle - alors vous êtes libre d'enfreindre la règle :)

Stephen Bailey
la source
0

Je ne vois pas comment la composition et la généralisation sont des alternatives et je n'ai pas pu trouver la citation .
La composition et l'héritage sont (pour atteindre la spécialisation), et on peut soutenir que l'abstraction et la généralisation sont (pour atteindre la modularité).

Ce que vous voulez, c'est que votre conception soit simple et plausible, deux qualités intrinsèquement assez faciles à mesurer.
Dans votre cas, il semble plus plausible de composer une ligne de deux points au lieu de l'étendre, car vous définiriez naturellement une ligne de deux points, plutôt qu'en étendant le concept de point.

back2dos
la source
Est-ce plus naturel? Une ligne n'est pas plus de deux points qu'elle n'est un seul point prolongé?
CarneyCode
2
Je n'ai jamais vu une définition de ligne qui n'inclut pas le fait qu'elle est composée de 2+ points. Même en utilisant une équation d'une ligne si vous n'avez qu'une seule valeur x dans votre domaine de nombres, c'est un point, pas une ligne.
Rig
0

La faveur se produit lorsque les deux candidats se qualifient. Le problème posé dans la question échoue à ce titre, car il n'a qu'une option de composition.

"La composition pourrait également utiliser la généralisation". Cette déclaration a-t-elle un sens pour nous? Sinon, nous ne sommes pas encore prêts à saisir la règle de la «faveur».

La raison pour laquelle nous privilégions la composition est qu'elle offre plus de possibilités d'extension / flexiblité que de généralisation. Cette extension / flexibilité se réfère principalement à la flexibilité d'exécution / dynamique (qui est obtenue avec la combinaison d'interfaces et de composition).

L'avantage n'est pas immédiatement visible. Pour voir l'avantage dont vous avez besoin d'attendre la prochaine demande de modification inattendue. Ainsi, dans la plupart des cas, ceux qui se sont attachés à la généralisation échouent par rapport à ceux qui ont adopté la composition (sauf un cas évident mentionné plus loin). D'où la règle. D'un point de vue d'apprentissage, si vous pouvez implémenter une injection de dépendance avec succès, vous devez savoir laquelle privilégier et quand. La règle vous aide à prendre une décision lorsque vous ne savez pas lequel choisir. Encore une fois, vous devriez pouvoir voir les deux options en premier lieu pour en privilégier une.

Résumé: Composition: le couplage est réduit en ayant simplement quelques petites choses que vous branchez sur quelque chose de plus grand, et le plus grand objet rappelle simplement le plus petit objet. Généralisation: Du point de vue d'une API tierce, définir qu'une méthode peut être remplacée est un engagement plus fort que de définir qu'une méthode peut être appelée (c'est sûr de gagner pour la généralisation). Et n'oubliez jamais qu'avec la composition, vous utilisez également la généralisation, à partir d'une interface au lieu d'une grande classe. Mais tout le mérite revient à la composition, malheureusement.

Blue Clouds
la source
-4

Aujourd'hui, j'ai écrit environ 300 LoC. Et je ne me souviens d'aucun principe que je pourrais violer et que je n'ai pas violé. Le refactoring sauvera mon âme, j'espère.

Si l'abstraction est dictée par l'API 3D Party - il est généralement correct d'utiliser l'héritage. Dans la conception domestique, l'entité doit être soit abstraite, soit scellée. L'entité abstraite doit potentiellement avoir environ 3 héritiers. Je n'aime pas les points d'extension d'une méthode virtuelle. Tout ce qui précède concerne mon goût. Je ne parle pas d'abstraction d'interface, qui est une histoire complètement différente.

Andrew_B
la source