web-dev-qa-db-fra.com

Pouvons-nous remplacer complètement l'héritage en utilisant un modèle de stratégie et une injection de dépendance?

Par exemple:

var duckBehaviors = new Duckbehavior();
duckBehaviors.quackBehavior = new Quack();
duckBehaviors.flyBehavior = new FlyWithWings();
Duck mallardDuck = new Duck(DuckTypes.MallardDuck, duckBehaviors)

Comme la classe Duck contient tous les comportements (abstraits), la création d'une nouvelle classe MallardDuck (qui étend Duck) ne semble pas nécessaire.

Référence: Head First Design Pattern, Chapitre 1.

10
Deepak Mishra

Bien sûr, mais nous appelons cela composition et délégation . Le modèle de stratégie et l'injection de dépendance peuvent sembler structurellement similaires, mais leurs intentions sont différentes.

Le modèle de stratégie permet de modifier l'exécution du comportement sous la même interface. Je pourrais dire à un canard colvert de voler et le regarder voler avec des ailes. Échangez-le ensuite contre un canard pilote et regardez-le voler avec Delta Airlines. Faire cela pendant que le programme est en cours d'exécution est une chose de modèle de stratégie.

L'injection de dépendances est une technique pour éviter les dépendances de codage en dur afin qu'elles puissent changer indépendamment sans exiger que les clients soient modifiés quand ils changent. Les clients expriment simplement leurs besoins sans savoir comment ils seront satisfaits. Ainsi, la manière dont ils sont atteints est décidée ailleurs (généralement dans le principal). Vous n'avez pas besoin de deux canards pour utiliser cette technique. Juste quelque chose qui utilise un canard sans savoir ni se soucier de quel canard. Quelque chose qui ne construit pas le canard ou ne le cherche pas, mais qui est parfaitement heureux d'utiliser le canard que vous lui tendez.

Si j'ai une classe de canard en béton, je peux la faire implémenter son comportement de mouche. Je pourrais même lui faire passer des comportements de voler avec des ailes à voler avec Delta en fonction d'une variable d'état. Cette variable pourrait être un booléen, un int, ou ce pourrait être un FlyBehavior qui a une méthode fly qui fait n'importe quel style de vol sans que je doive le tester avec un if. Maintenant, je peux changer de style de vol sans changer de type de canard. Maintenant, les canards colverts peuvent devenir pilotes. Il s'agit de composition et délégation . Le canard est composé d'un FlyBehavior et il peut lui déléguer des demandes de vol. Vous pouvez remplacer tous vos comportements de canard à la fois de cette façon ou détenir quelque chose pour chaque comportement ou toute combinaison entre les deux.

Cela vous donne tous les mêmes pouvoirs que l'héritage, sauf un. L'héritage vous permet d'exprimer les méthodes Duck que vous remplacez dans les sous-types Duck. La composition et la délégation nécessitent que le canard délègue explicitement aux sous-types dès le départ. C'est beaucoup plus flexible mais cela implique plus de frappe au clavier et Duck doit savoir que cela se produit.

Cependant, beaucoup de gens pensent que l'héritage doit être explicitement conçu dès le départ. Et que si ce n'est pas le cas, vous devez marquer vos classes comme scellées/finales pour interdire l'héritage. Si vous adoptez ce point de vue, l'héritage n'a vraiment aucun avantage sur la composition et la délégation. Parce que dans tous les cas, vous devez soit concevoir une extensibilité dès le début, soit être prêt à démolir les choses plus tard.

Démolir les choses est en fait une option populaire. Sachez simplement qu'il y a des cas où c'est un problème. Si vous avez déployé indépendamment des bibliothèques ou des modules de code que vous n'avez pas l'intention de mettre à jour avec la prochaine version, vous pouvez vous retrouver avec des versions de classes qui ne savent rien de ce que vous êtes en train de faire.

Bien que vouloir démolir les choses plus tard puisse vous libérer de la conception excessive, il est très puissant de pouvoir concevoir quelque chose qui utilise un canard sans avoir à savoir ce que le canard fera réellement lorsqu'il sera utilisé. Ce non-savoir est un truc puissant. Il vous permet de cesser de penser aux canards pendant un certain temps et de penser au reste de votre code.

"Pouvons-nous" et "devrions-nous" sont des questions différentes. Favoriser la composition plutôt que l'héritage ne dit pas de ne jamais utiliser l'héritage. Il y a encore des cas où l'héritage a le plus de sens. Je vais vous montrer mon exemple préféré :

public class LoginFailure : System.ApplicationException {}

L'héritage vous permet de créer des exceptions avec des noms descriptifs plus spécifiques sur une seule ligne.

Essayez de faire cela avec la composition et vous obtiendrez un gâchis. De plus, il n'y a aucun risque d'héritage problème yo-yo car il n'y a pas de données ou de méthodes ici pour réutiliser et encourager le chaînage d'héritage. Tout cela ajoute un bon nom. Ne sous-estimez jamais la valeur d'une bonne réputation.

21
candied_orange

Vous pouvez remplacer presque n'importe quelle méthodologie par n'importe quelle autre méthodologie et toujours produire un logiciel fonctionnel. Pourtant, certains sont mieux adaptés à un problème particulier que d'autres.

Cela dépend de beaucoup de choses que l'on est préférable. Art antérieur dans l'application, expérience dans l'équipe, développements futurs attendus, préférence personnelle et combien il sera difficile pour un nouvel arrivant de s'y mettre, pour n'en nommer que quelques-uns.

Au fur et à mesure que vous devenez plus expérimenté et que vous vous débattez plus souvent avec les créations des autres, vous mettrez probablement plus l'accent sur la dernière contemplation.

L'héritage est toujours un outil de modélisation valide et solide qui n'est pas nécessairement toujours le plus flexible, mais il offre des conseils solides aux nouveaux utilisateurs qui peuvent être reconnaissants de la correspondance claire avec le domaine problématique.

7
Martin Maat