«Préférez la composition à l'héritage» - Est-ce la seule raison de se défendre contre les changements de signature?

13

Cette page préconise la composition plutôt que l'héritage avec l'argument suivant (reformulé dans mes mots):

Un changement dans la signature d'une méthode de la superclasse (qui n'a pas été remplacée dans la sous-classe) provoque des changements supplémentaires à de nombreux endroits lorsque nous utilisons l'héritage. Cependant, lorsque nous utilisons Composition, le changement supplémentaire requis ne se fait qu'à un seul endroit: la sous-classe.

Est-ce vraiment la seule raison de privilégier la composition à l'héritage? Parce que si c'est le cas, ce problème peut être facilement résolu en appliquant un style de codage qui préconise de remplacer toutes les méthodes de la superclasse, même si la sous-classe ne change pas l'implémentation (c'est-à-dire, en mettant des remplacements factices dans la sous-classe). Est-ce que j'ai râté quelque chose?

Utku
la source
1
voir aussi: Discutez de ce $ {blog}
moucher
2
La citation ne dit pas que c'est "la seule" raison.
Tulains Córdova
3
La composition est presque toujours meilleure, car l'héritage ajoute généralement de la complexité, du couplage, de la lecture de code et réduit la flexibilité. Mais il y a des exceptions notables, parfois une classe de base ou deux ne fait pas de mal. C'est pourquoi son « préfère » plutôt que « toujours ».
Mark Rogers

Réponses:

27

J'aime les analogies, en voici une: avez-vous déjà vu un de ces téléviseurs avec lecteur de magnétoscope intégré? Que diriez-vous de celui avec un magnétoscope et un lecteur DVD? Ou celui qui a un Blu-ray, un DVD et un magnétoscope. Oh et maintenant tout le monde est en streaming donc nous devons concevoir un nouveau téléviseur ...

Très probablement, si vous possédez un téléviseur, vous n'en avez pas comme celui ci-dessus. La plupart d'entre eux n'ont probablement jamais existé. Vous avez très probablement un moniteur ou un ensemble qui a de nombreuses entrées afin que vous n'ayez pas besoin d'un nouvel ensemble chaque fois qu'il y a un nouveau type de périphérique d'entrée.

L'héritage est comme le téléviseur avec le magnétoscope intégré. Si vous avez 3 types de comportements différents que vous souhaitez utiliser ensemble et que chacun a 2 options pour l'implémentation, vous avez besoin de 8 classes différentes afin de représenter tous ces comportements. Au fur et à mesure que vous ajoutez d'autres options, les chiffres explosent. Si vous utilisez plutôt la composition, vous évitez ce problème combinatoire et la conception aura tendance à être plus extensible.

JimmyJames
la source
6
Il y avait beaucoup de téléviseurs avec magnétoscope et lecteur DVD intégrés à la fin des années 90 et au début des années 2000.
JAB
@JAB et de nombreux téléviseurs (la plupart?) Vendus aujourd'hui prennent en charge le streaming, d'ailleurs.
Casey
4
@JAB Et aucun d'entre eux ne peut vendre son lecteur DVD pour acheter un lecteur Bluray
Alexander - Reinstate Monica
1
@JAB Droite. La déclaration "Avez-vous déjà vu un de ces téléviseurs avec lecteur de magnétoscope intégré?" implique qu'ils existent.
JimmyJames
4
@Casey Parce qu'évidemment, il n'y aura jamais une autre technologie qui remplacera celle-ci, non? Je parie que l'un de ces téléviseurs prend également en charge les entrées d'appareils externes au cas où quelqu'un voudrait utiliser autre chose sans acheter un nouveau téléviseur. Ou plutôt, peu d'acheteurs instruits sont prêts à acheter un téléviseur dépourvu de telles entrées. Ma prémisse n'est pas que ces approches n'existent pas et je ne dirais pas non plus que l'héritage n'existe pas. Le fait est que ces approches sont intrinsèquement moins flexibles que la composition.
JimmyJames
4

Non, ça ne l'est pas. D'après mon expérience, la composition sur l'héritage est le résultat de penser votre logiciel comme un tas d' objets avec des comportements qui se parlent / des objets qui se fournissent des services au lieu de classes et données .

De cette façon, vous avez des objets qui fournissent des serviecs. Vous les utilisez comme blocs de construction (vraiment comme Lego) pour activer d'autres comportements (par exemple, un UserRegistrationService utilise les services fournis par un EmailerService et un UserRepository ).

Lors de la construction de systèmes plus grands avec cette approche, j'ai naturellement utilisé l'héritage dans très peu de cas. À la fin de la journée, l'héritage est un outil très puissant qui doit être utilisé avec prudence et uniquement lorsque cela est approprié.

marstato
la source
Je vois la mentalité Java ici. La POO concerne les données qui sont regroupées avec les fonctions qui y agissent - ce que vous décrivez est mieux appelé «modules». Vous avez raison de dire qu'éviter l'héritage est une bonne idée lorsque vous réaffectez des objets en modules, mais je trouve troublant que vous sembliez recommander cela comme la manière préférée d'utiliser des objets.
Brilliand
1
@Brilliand Si vous saviez seulement combien de code java est écrit dans le style que vous décrivez et combien d'horreur c'est ... Smalltalk a introduit le terme POO et il a été conçu autour d'objets recevant des messages. : "L'informatique doit être considérée comme une capacité intrinsèque des objets qui peuvent être invoqués uniformément en envoyant des messages." Il n'est fait aucune mention des données et des fonctions qui y sont exploitées. Désolé, mais à mon avis, vous vous trompez gravement. S'il ne s'agissait que de données et de fonctions, on pourrait continuer à écrire des structures avec plaisir.
marstato
@Tsar malheureusement, même si Java prend en charge les méthodes statiques, la culture Java ne le fait pas
k_g
@Brilliand Qui se soucie de ce qu'est la POO? Ce qui importe, c'est comment vous pouvez l' utiliser et les résultats de son utilisation de cette manière.
user253751
1
Après discussion avec @Tsar: les classes Java ne doivent pas être instanciées, et en effet, les classes telles que UserRegistrationService et EmailService ne devraient généralement pas être instanciées - les classes sans données associées fonctionnent mieux comme classes statiques (et en tant que telles, ne sont pas pertinentes pour l'original question). Je vais supprimer certains de mes commentaires précédents, mais ma critique initiale de la réponse de marsato est valable.
Brilliand
4

« Préférez composition de l' héritage » est juste une bonne heuristique

Vous devriez considérer le contexte, car ce n'est pas une règle universelle. Ne le prenez pas pour signifier ne jamais utiliser l'héritage quand vous pouvez faire de la composition. Si tel était le cas, vous le corrigeriez en interdisant l'héritage.

J'espère clarifier ce point le long de ce post.

Je n'essaierai pas de défendre le mérite de la composition par lui-même. Ce que je considère hors sujet. Au lieu de cela, je parlerai de certaines situations où le développeur peut envisager un héritage qui serait préférable d'utiliser la composition. Sur cette note, l'héritage a ses propres mérites, que je considère également hors sujet.


Exemple de véhicule

J'écris sur des développeurs qui essaient de faire les choses de façon stupide, à des fins narratives

Allons pour une variante des exemples classiques que certains cours POO utilisent ... Nous avons une Vehicleclasse, nous dérivons Car, Airplane, Balloonet Ship.

Remarque : Si vous devez mettre cet exemple à la terre, faites comme s'il s'agissait de types d'objets dans un jeu vidéo.

Ensuite, Caret Airplanepeuvent avoir un code commun, car ils peuvent tous les deux rouler sur terre. Les développeurs peuvent envisager de créer une classe intermédiaire dans la chaîne d'héritage pour cela. Pourtant, en fait, il existe également un code partagé entre Airplaneet Balloon. Ils pourraient envisager de créer une autre classe intermédiaire dans la chaîne d'héritage pour cela.

Ainsi, le développeur serait à la recherche d'héritage multiple. Au point où les développeurs recherchent l'héritage multiple, la conception a déjà mal tourné.

Il est préférable de modéliser ce comportement sous forme d'interfaces et de composition, afin de pouvoir le réutiliser sans avoir à exécuter l'héritage de plusieurs classes. Si les développeurs, par exemple, créent la FlyingVehiculeclasse. Ils diraient que Airplanec'est un FlyingVehicule(héritage de classe), mais nous pourrions plutôt dire qu'il Airplanea un Flyingcomposant (composition) et Airplaneest unIFlyingVehicule (héritage d'interface).

En utilisant des interfaces, si nécessaire, nous pouvons avoir plusieurs héritages (d'interfaces). De plus, vous n'êtes pas couplé à une implémentation particulière. Augmenter la réutilisabilité et la testabilité de votre code.

N'oubliez pas que l'héritage est un outil de polymorphisme. De plus, le polymorphisme est un outil de réutilisation. Si vous pouvez augmenter la réutilisabilité de votre code en utilisant la composition, faites-le. Si vous n'êtes pas certain que la composition offre ou non une meilleure réutilisabilité, "Préférez la composition à l'héritage" est une bonne heuristique.

Tout cela sans le mentionner Amphibious.

En fait, nous n'avons peut-être pas besoin de choses qui décollent. Stephen Hurn a un exemple plus éloquent dans ses articles «Favoriser la composition sur l'héritage», partie 1 et partie 2 .


Substituabilité et encapsulation

Doit Ahériter ou composer B?

Si Aune spécialisation de Bcelui-ci doit respecter le principe de substitution de Liskov , l'hérédité est viable, voire souhaitable. S'il y a des situations où il An'y a pas de substitution valide, Balors nous ne devrions pas utiliser l'héritage.

Nous pourrions être intéressés par la composition comme une forme de programmation défensive, pour défendre la classe dérivée . En particulier, une fois que vous commencez à utiliser Bà d'autres fins, il peut y avoir une pression pour le modifier ou l'étendre pour qu'il soit plus adapté à ces fins. S'il y a un risque qui Bpeut exposer des méthodes qui pourraient entraîner un état invalide, Anous devrions utiliser la composition au lieu de l'héritage. Même si nous sommes l'auteur des deux Bet A, c'est une chose de moins à s'inquiéter, donc la composition facilite la réutilisation de B.

Nous pouvons même affirmer que s'il y a des fonctionnalités Bqui An'en ont pas besoin (et nous ne savons pas si ces fonctionnalités pourraient entraîner un état non valide pourA , dans la mise en œuvre actuelle ou à l'avenir), c'est une bonne idée d'utiliser la composition au lieu de l'héritage.

La composition présente également les avantages de permettre la mise en œuvre des commutations et de faciliter la simulation.

Remarque : il existe des situations où nous souhaitons utiliser la composition malgré la validité de la substitution. Nous archivons cette substituabilité à l'aide d'interfaces ou de classes abstraites (laquelle utiliser quand est un autre sujet), puis utilisons la composition avec injection de dépendance de l'implémentation réelle.

Enfin, bien sûr, il y a l'argument selon lequel nous devrions utiliser la composition pour défendre la classe parent parce que l'héritage rompt l'encapsulation de la classe parent:

L'héritage expose une sous-classe aux détails de l'implémentation de son parent, on dit souvent que «l'héritage rompt l'encapsulation»

- Modèles de conception: éléments de logiciels orientés objet réutilisables, groupe de quatre

Eh bien, c'est une classe parent mal conçue. C'est pourquoi vous devriez:

Concevez pour l'héritage, ou interdisez-le.

- Java efficace, Josh Bloch


Problème Yo-Yo

Un autre cas où la composition aide est le problème Yo-Yo . Ceci est une citation de Wikipedia:

Dans le développement logiciel, le problème yo-yo est un anti-modèle qui se produit lorsqu'un programmeur doit lire et comprendre un programme dont le graphe d'héritage est si long et compliqué qu'il doit continuer à basculer entre de nombreuses définitions de classes différentes afin de suivre le flux de contrôle du programme.

Vous pouvez résoudre, par exemple: Votre classe Cn'héritera pas de la classe B. Au lieu de cela, votre classe Caura un membre de type A, qui peut ou non être (ou avoir) un objet de type B. De cette façon, vous ne programmerez pas contre le détail de mise en œuvre de B, mais contre le contrat que Apropose l'interface (de) .


Exemples de compteur

De nombreux cadres favorisent l'héritage plutôt que la composition (ce qui est le contraire de ce que nous avons soutenu). Le développeur peut effectuer cette opération car il a mis beaucoup de travail dans sa classe de base que son implémentation avec la composition aurait augmenté la taille du code client. Parfois, cela est dû aux limitations de la langue.

Par exemple, un framework PHP ORM peut créer une classe de base qui utilise des méthodes magiques pour permettre l'écriture de code comme si l'objet avait des propriétés réelles. Au lieu de cela, le code géré par les méthodes magiques ira dans la base de données, recherchera le champ particulier demandé (peut-être le mettra en cache pour une demande future) et le renverra. Le faire avec la composition nécessiterait que le client crée des propriétés pour chaque champ ou écrive une version du code des méthodes magiques.

Addendum : Il existe d'autres façons de permettre l'extension des objets ORM. Ainsi, je ne pense pas que l'héritage soit nécessaire dans ce cas. C'est moins cher.

Pour un autre exemple, un moteur de jeu vidéo peut créer une classe de base qui utilise du code natif en fonction de la plate-forme cible pour effectuer le rendu 3D et la gestion des événements. Ce code est complexe et spécifique à la plate-forme. Il serait coûteux et sujet aux erreurs pour l'utilisateur développeur du moteur de gérer ce code, en fait, cela fait partie de la raison d'utiliser le moteur.

De plus, sans la partie de rendu 3D, c'est ainsi que de nombreux frameworks de widgets fonctionnent. Cela vous évite de vous soucier de la gestion des messages du système d'exploitation… en fait, dans de nombreuses langues, vous ne pouvez pas écrire un tel code sans une forme d'enchère native. De plus, si vous le faisiez, cela restreindrait votre portabilité. Au lieu de cela, avec héritage, à condition que le développeur ne casse pas la compatibilité (trop); vous pourrez à l'avenir facilement porter votre code sur toutes les nouvelles plates-formes qu'ils prennent en charge.

De plus, considérez que plusieurs fois nous ne voulons remplacer que quelques méthodes et laisser tout le reste avec les implémentations par défaut. Si nous avions utilisé la composition, nous aurions dû créer toutes ces méthodes, ne serait-ce que pour déléguer à l'objet encapsulé.

Par cet argument, il y a un point où la composition peut être pire pour la maintenabilité que l'héritage (lorsque la classe de base est trop complexe). Cependant, rappelez-vous que la maintenabilité de l'héritage peut être pire que celle de la composition (lorsque l'arbre d'héritage est trop complexe), ce à quoi je fais référence dans le problème du yo-yo.

Dans les exemples présentés, les développeurs ont rarement l'intention de réutiliser le code généré via l'héritage dans d'autres projets. Cela atténue la réutilisabilité réduite de l'utilisation de l'héritage au lieu de la composition. De plus, en utilisant l'héritage, les développeurs de framework peuvent fournir beaucoup de code facile à utiliser et à découvrir.


Dernières pensées

Comme vous pouvez le voir, la composition a un certain avantage sur l'héritage dans certaines situations, pas toujours. Il est important de considérer le contexte et les différents facteurs impliqués (tels que la réutilisabilité, la maintenabilité, la testabilité, etc.) pour prendre la décision. Retour au premier point: « Préférez composition sur l' héritage » est un juste bonne heuristique.

Vous pouvez également remarquer que la plupart des situations que je décris peuvent être résolues dans une certaine mesure avec Traits ou Mixins. Malheureusement, ce ne sont pas des fonctionnalités courantes dans la grande liste des langues, et elles s'accompagnent généralement d'un certain coût de performance. Heureusement, leurs méthodes d'extension et implémentations par défaut populaires de leurs cousins ​​atténuent certaines situations.

J'ai un article récent où je parle de certains des avantages des interfaces dans pourquoi avons-nous besoin d'interfaces entre l'interface utilisateur, les entreprises et l'accès aux données en C # . Il aide au découplage et facilite la réutilisabilité et la testabilité, cela pourrait vous intéresser.

Theraot
la source
Euh ... Le framework Net utilise plusieurs types de flux hérités pour effectuer son travail lié au flux. Il fonctionne incroyablement bien et il est très facile à utiliser et à étendre. Votre exemple n'est pas très bon ...
T. Sar
1
@TSar "il est super facile à utiliser et à étendre" Avez-vous déjà essayé de lancer le flux de sortie standard vers Debug? C'est ma seule expérience à essayer d'étendre leurs flux. Ce n'était pas facile. Ce fut un cauchemar qui s'est terminé en moi en remplaçant explicitement chaque méthode de la classe. Extrêmement répétitif. Et si vous regardez le code source .NET, remplacer tout explicitement est à peu près comment ils le font. Je ne suis donc pas convaincu que ce soit toujours aussi simple à étendre.
jpmc26
Il est préférable de lire et d'écrire une structure à partir d'un flux avec des fonctions gratuites ou des méthodes multiples.
user253751
@ jpmc26 Je suis désolé que votre expérience n'ait pas été très bonne. Cependant, je n'ai eu aucun problème à utiliser ou à mettre en œuvre des éléments liés au flux pour différentes choses.
T. Sar
3

Normalement, l'idée directrice de Composition-Over-Inheritance est de fournir une plus grande flexibilité de conception et de ne pas réduire la quantité de code que vous devez modifier pour propager un changement (que je trouve douteux).

L'exemple sur wikipedia donne un meilleur exemple de la puissance de son approche:

En définissant une interface de base pour chaque "morceau de composition", vous pourriez facilement créer divers "objets composés" avec une variété qui pourrait être difficile à atteindre avec l'héritage seul.

lbenini
la source
Donc, cette section donne un exemple de composition, non? Je demande parce que ce n'est pas évident dans l'article.
Utku
1
Oui, "Composition sur héritage" ne signifie pas que vous n'avez pas d'interfaces, si vous regardez l'objet Player, vous pouvez voir qu'il s'intègre bien dans une hiérarchie, mais le code (excusez la brutalité) ne vient pas de l'héritage, mais de la composition avec une classe qui fait le travail
lbenini
2

La ligne: "Préférez la composition à l'héritage" n'est rien d'autre qu'un coup de pouce dans la bonne direction. Cela ne vous sauvera pas de votre propre stupidité et vous donnera en fait une chance d'aggraver les choses. C'est parce qu'il y a beaucoup de pouvoir ici.

La principale raison pour laquelle cela doit même être dit est que les programmeurs sont paresseux. Paresseux, ils prendront toute solution qui signifie moins de frappe au clavier. C'est le principal argument de vente de l'héritage. Je tape moins aujourd'hui et quelqu'un d'autre se fait baiser demain. Alors quoi, je veux rentrer à la maison.

Le lendemain, le patron insiste pour que j'utilise la composition. N'explique pas pourquoi mais y insiste. Je prends donc une instance de ce dont je héritais, expose une interface identique à celle dont je héritais et implémente cette interface en déléguant tout le travail à la chose dont je héritais. C'est tout un typage cérébral qui aurait pu être fait avec un outil de refactoring et qui me semble inutile mais le patron le voulait donc je le fais.

Le lendemain, je demande si c'est ce que l'on voulait. Que pensez-vous que le patron dit?

À moins qu'il ne soit nécessaire de pouvoir modifier dynamiquement (au moment de l'exécution) ce qui était hérité de (voir modèle d'état), c'était une énorme perte de temps. Comment le patron peut-il dire cela et être toujours en faveur de la composition plutôt que de l'héritage?

En plus de ne rien faire pour éviter la casse due aux changements de signature de méthode, cela manque complètement le plus grand avantage de la composition. Contrairement à l'héritage, vous êtes libre de créer une interface différente . Un plus approprié à la façon dont il sera utilisé à ce niveau. Vous savez, l'abstraction!

Il y a quelques avantages mineurs à remplacer automatiquement l'héritage par la composition et la délégation (et certains inconvénients), mais garder le cerveau éteint pendant ce processus manque une énorme opportunité.

L'indirection est très puissante. Fais-en bon usage.

candied_orange
la source
0

Voici une autre raison: dans de nombreux langages OO (je pense à ceux comme C ++, C # ou Java ici), il n'y a aucun moyen de changer la classe d'un objet une fois que vous l'avez créé.

Supposons que nous créons une base de données des employés. Nous avons une classe d'employés de base abstraite et commençons à dériver de nouvelles classes pour des rôles spécifiques - ingénieur, gardien de sécurité, chauffeur de camion, etc. Chaque classe a un comportement spécialisé pour ce rôle. Tout est bon.

Un jour, un ingénieur est promu ingénieur. Vous ne pouvez pas reclasser un ingénieur existant. Au lieu de cela, vous devez écrire une fonction qui crée un gestionnaire d'ingénierie, copiant toutes les données de l'ingénieur, supprime l'ingénieur de la base de données, puis ajoute le nouveau gestionnaire d'ingénierie. Et vous devez écrire une telle fonction pour chaque changement de rôle possible.

Pire encore, supposons qu'un chauffeur de camion part en congé de maladie de longue durée et qu'un gardien de sécurité propose de faire la conduite de camion à temps partiel pour remplir.

Il est tellement plus facile de créer une classe Employee concrète et de la traiter comme un conteneur auquel vous pouvez ajouter des propriétés, telles que le rôle du travail.

Simon B
la source
Ou vous créez une classe Employee avec un pointeur sur une liste de rôles, et chaque travail réel étend le rôle. Votre exemple pourrait être fait pour utiliser l'héritage d'une manière très bonne et sensée sans trop de travail.
T. Sar
0

L'héritage fournit deux choses:

1) Réutilisation du code: peut être accompli facilement grâce à la composition. Et en fait, est mieux réalisé grâce à la composition car il préserve mieux l'encapsulation.

2) Polymorphisme: peut être accompli via des "interfaces" (classes où toutes les méthodes sont purement virtuelles).

Un argument solide pour utiliser l'héritage non-interface est lorsque le nombre de méthodes requises est important et que votre sous-classe ne souhaite en modifier qu'un petit sous-ensemble. Cependant, en général, votre interface devrait avoir de très petites empreintes si vous souscrivez au principe de "responsabilité unique" (vous devriez), donc ces cas devraient être rares.

Le fait d'avoir le style de codage nécessitant de remplacer toutes les méthodes de classe parente n'évite pas de toute façon d'écrire un grand nombre de méthodes d'intercommunication, alors pourquoi ne pas réutiliser le code via la composition et obtenir l'avantage supplémentaire de l'encapsulation? De plus, si la super-classe devait être étendue en ajoutant une autre méthode, le style de codage nécessiterait l'ajout de cette méthode à toutes les sous-classes (et il y a de fortes chances que nous violions alors la "ségrégation d'interface" ). Ce problème se développe rapidement lorsque vous commencez à avoir plusieurs niveaux d'héritage.

Enfin, dans de nombreux langages, le test unitaire des objets basés sur la "composition" est beaucoup plus facile que le test unitaire des objets basés sur "l'héritage" en remplaçant l'objet membre par un objet factice / test / factice.

dlasalle
la source
0

Est-ce vraiment la seule raison de privilégier la composition à l'héritage?

Nan.

Il existe de nombreuses raisons de privilégier la composition à l'héritage. Il n'y a pas une seule bonne réponse. Mais je vais jeter une autre raison de privilégier la composition à l'héritage dans le mix:

Privilégier la composition à l'héritage améliore la lisibilité de votre code , ce qui est très important lorsque vous travaillez sur un grand projet avec plusieurs personnes.

Voici comment j'y pense: quand je lis le code de quelqu'un d'autre, si je vois qu'il a étendu une classe, je vais demander quel comportement dans la super classe change-t-il?

Et puis je vais parcourir leur code, à la recherche d'une fonction remplacée. Si je n'en vois pas, je le classe comme une mauvaise utilisation de l'héritage. Ils auraient dû utiliser la composition à la place, ne serait-ce que pour me sauver (et toutes les autres personnes de l'équipe) de 5 minutes de lecture de leur code!

Voici un exemple concret: en Java, la JFrameclasse représente une fenêtre. Pour créer une fenêtre, vous créez une instance de JFramepuis appelez des fonctions sur cette instance pour lui ajouter des éléments et l'afficher.

De nombreux didacticiels (même les plus officiels) vous recommandent d'étendre JFrameafin de pouvoir traiter les instances de votre classe comme des instances de JFrame. Mais à mon humble avis (et l'avis de beaucoup d'autres), c'est une assez mauvaise habitude à prendre! Au lieu de cela, vous devez simplement créer une instance de JFrameet appeler des fonctions sur cette instance. Il n'est absolument pas nécessaire d'étendre JFramedans cette instance, car vous ne modifiez aucun des comportements par défaut de la classe parente.

D'un autre côté, une façon d'effectuer une peinture personnalisée consiste à étendre la JPanelclasse et à remplacer la paintComponent()fonction. C'est une bonne utilisation de l'héritage. Si je regarde dans votre code et que je vois que vous avez étendu JPanelet remplacé la paintComponent()fonction, je saurai exactement quel comportement vous changez.

Si c'est juste vous qui écrivez du code par vous-même, alors il pourrait être aussi facile d'utiliser l'héritage que d'utiliser la composition. Mais faites comme si vous étiez dans un environnement de groupe et que d'autres personnes liront votre code. Privilégier la composition à l'héritage facilite la lecture de votre code.

Kevin Workman
la source
L'ajout d'une classe d'usine entière avec une seule méthode qui renvoie une instance d'un objet afin que vous puissiez utiliser la composition au lieu de l'héritage ne rend pas vraiment votre code plus lisible ... (Oui, vous en avez besoin si vous avez besoin d'une nouvelle instance à chaque fois une méthode particulière est appelée.) Cela ne signifie pas que l'héritage est bon, mais la composition n'améliore pas toujours la lisibilité.
jpmc26
1
@ jpmc26 ... pourquoi diable auriez-vous besoin d'une classe d'usine juste pour créer une nouvelle instance d'une classe? Et comment l'héritage améliore-t-il la lisibilité de ce que vous décrivez?
Kevin Workman
Ne vous ai-je pas explicitement donné une raison pour une telle usine dans le commentaire ci-dessus? Il est plus lisible car il en résulte moins de code à lire . Vous pouvez accomplir le même comportement avec une seule méthode abstraite dans la classe de base et quelques sous-classes. (De toute façon, vous alliez avoir une implémentation différente de votre classe composée.) Si les détails de la construction de l'objet sont fortement liés à la classe de base, il ne trouvera pas beaucoup d'utilisation ailleurs dans votre code, ce qui réduira davantage les avantages de faire une classe séparée. Ensuite, il conserve les parties de code étroitement liées les unes aux autres.
jpmc26
Comme je l'ai dit, cela ne signifie pas nécessairement que l'héritage est bon, mais cela illustre comment la composition n'améliore pas la lisibilité dans certaines situations.
jpmc26
1
Sans voir un exemple spécifique, il est difficile de vraiment commenter. Mais ce que vous avez décrit me semble être un cas de suringénierie. Besoin d'une instance d'une classe? Le newmot-clé vous en donne un. Quoi qu'il en soit, merci pour la discussion.
Kevin Workman