J'essaie de m'enseigner le génie logiciel et je me heurte à des informations contradictoires qui me déroutent.
J'ai appris la POO et ce que sont les classes / interfaces abstraites et comment les utiliser, mais je lis ensuite qu'il faut «privilégier la composition plutôt que l'héritage». Je comprends que la composition, c'est quand une classe compose / crée un objet d'une autre classe pour utiliser / interagir avec les fonctionnalités de ce nouvel objet.
Donc ma question est ... Suis-je censé ne pas utiliser de classes et d'interfaces abstraites? Ne pas créer une classe abstraite et étendre / hériter la fonctionnalité de cette classe abstraite dans les classes concrètes, et au lieu de cela, simplement composer de nouveaux objets pour utiliser les fonctionnalités de l'autre classe?
Ou suis-je censé utiliser la composition ET hériter des classes abstraites; les utiliser les deux conjointement? Si oui, pourriez-vous fournir des exemples de la façon dont cela fonctionnerait et de ses avantages?
Comme je suis plus à l'aise avec PHP, je l'utilise pour améliorer mes compétences OOP / SE avant de passer à d'autres langues et de transférer mes compétences SE nouvellement acquises, donc des exemples utilisant PHP seraient les plus appréciés.
la source
Réponses:
La composition sur l'héritage signifie que lorsque vous souhaitez réutiliser ou étendre les fonctionnalités d'une classe existante, il est souvent plus approprié de créer une autre classe qui «encapsulera» la classe existante et utilisera son implémentation en interne. Le motif décorateur en est un exemple.
L'héritage n'est pas un bon moyen par défaut pour gérer les scénarios de réutilisation, car vous voulez souvent utiliser seulement une partie de la fonctionnalité d'une classe de base et la sous-classe ne peut pas prendre en charge l'intégralité du contrat de la classe de base d'une manière qui satisfait la substitution Liskov principe .
La méthode de modèle est un bon exemple de cas où l'héritage est approprié.
Consultez cette réponse pour obtenir des instructions sur le choix entre la composition et l'héritage.
la source
Je ne connais pas assez bien PHP pour vous donner des exemples concrets dans ce langage, mais voici quelques lignes directrices que j'ai trouvées utiles.
Les interfaces / traits sont utiles pour définir le comportement à travers de nombreuses classes qui peuvent avoir très peu d'autre en commun.
Par exemple, si vous travaillez sur une API JSON, vous pouvez définir une interface qui spécifie deux méthodes: «toJson» et «toStatusCode». Cela vous permettrait d'écrire un assistant qui peut prendre n'importe quel objet qui implémente cette interface et le convertir en réponse Http.
La plupart du temps, cela est préférable à l'héritage direct, car il est moins susceptible de souffrir du problème de classe de base fragile, car il tend vers des hiérarchies de classe peu profondes.
L'héritage direct est mieux pensé comme quelque chose que vous faites lorsqu'il y a des raisons impérieuses de le faire, plutôt que comme quelque chose que vous faites à moins qu'il y ait des raisons impérieuses de ne pas le faire.
Dans les langues qui ne prennent pas en charge les implémentations par défaut dans les interfaces, cela peut être un moyen raisonnable de partager un ensemble de fonctionnalités de base, mais cela limite généralement fortement ce que vous pouvez faire avec les sous-classes (l'héritage multiple, lorsqu'il est disponible, vaut rarement la peine) . Souvent, je trouve que cela vaut la peine d'écrire les méthodes de redirection standard pour utiliser la composition à la place.
La composition doit être votre stratégie par défaut. Autant que possible, composez avec des interfaces et passez-les comme arguments de constructeur. Cela vous facilitera la vie lors de la rédaction des tests.
Évitez autant que possible de travailler avec des singletons globaux, car comparer la sortie attendue et la sortie réelle est un vrai problème si une partie de la sortie dépend de l'état de l'horloge système.
Ce n'est bien sûr pas toujours possible. Par exemple, le framework Web Play fournit une bibliothèque JSON qui est essentiellement un langage spécifique au domaine. Changer cela serait une entreprise majeure, dont le remplacement des appels à «Json.prettyPrint» ne serait qu'une très petite partie.
la source
La composition est lorsqu'une classe offre des fonctionnalités en instanciant une classe (éventuellement interne) qui implémente déjà cette fonctionnalité, au lieu d'hériter de cette classe.
Ainsi, par exemple, si vous avez une classe qui modélise un navire et qu'on vous dit maintenant que votre navire devrait offrir un héliport, il n'est pas naturel de dériver votre navire d'un héliport, (duh!) À la place, vous devriez avoir votre vaisseau contient une classe d'héliport et l'expose via une
Ship.getHelipad()
méthode.Autrefois (il y a une dizaine d'années), les gens considéraient l'héritage comme un moyen rapide et facile d'agréger les fonctionnalités, il y avait donc de nombreux exemples du type "navire héritant de l'héliport", qui étaient, bien sûr, très boiteux.
Mais le dicton "Favoriser la composition plutôt que l'héritage" a été soigneusement rédigé de manière à indiquer clairement qu'il ne s'agit que d'une suggestion, et non d'une règle. L'auteur du dicton a fait preuve de suffisamment de prudence pour s'abstenir de dire quelque chose comme "tu n'utiliseras jamais l' héritage, seulement la composition". Cela porte essentiellement à l'attention de la communauté du génie logiciel le fait que l'héritage a été surutilisé, alors que dans de nombreux cas, la composition produit des conceptions plus claires, élégantes et maintenables que l'héritage.
Donc, essentiellement, le dicton "Favoriser la composition sur l'héritage" suggère que chaque fois que vous êtes confronté au "hériter ou composer?" question, vous devriez réfléchir sérieusement à la stratégie la plus appropriée, et que la plupart des chances sont que la stratégie la plus appropriée se révélera être la composition, pas l'héritage.
Mais comme ce n'est pas une règle, vous devez également garder à l'esprit qu'il existe de nombreux cas où l'héritage est plus naturel. Si vous utilisez la composition là où vous auriez dû utiliser l'héritage, de nombreux maux arriveront dans votre code.
Pour revenir à l'exemple du vaisseau, si votre vaisseau a besoin d'offrir une interface pour interagir avec un
FloatingMachine
, alors il est plus naturel de le dériver d'uneFloatingMachine
classe abstraite , qui pourrait très probablement dériver à son tour d'une autreMachine
classe abstraite .Voici la règle générale pour la réponse à la question de la composition par rapport à l'héritage:
Un navire "est une" machine flottante, et une machine flottante "est une" machine. L'héritage est donc parfaitement bien pour ceux-là. Mais un navire n'est pas, bien sûr, un héliport. Il est donc préférable de composer la fonctionnalité de l'héliport.
la source