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


10

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 s'étend Duck) ne semble pas nécessaire.

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


Quels sont les types Duckbehavior.quackBehavioret les autres champs de votre code?
max630

Il n'y a pas d'injection de dépendance dans votre exemple.
David Conrad

11
L'héritage est génial lorsque vous êtes un développeur de niveau junior à intermédiaire, car il existe une longue histoire de refactoring des astuces et des modèles de conception autour. Mais la plupart des développeurs expérimentés avec qui j'ai parlé préfèrent des hiérarchies d'héritage vraiment superficielles basées sur la composition chaque fois que possible. L'héritage peut entraîner un couplage plus serré que nécessaire et peut rendre les changements difficiles.
Mark Rogers


5
@DavidConrad: L'OP ne fait pas de nouveautés dans la classe . DI / IoC consiste à injecter des dépendances, pas à propos de conteneurs ou à ne jamais utiliser "new"; c'est une préoccupation entièrement orthogonale. De plus, vous devez toujours changer le code quelque part - que ce soit la racine de composition ou un fichier de configuration. Le contrôle est inversé ici dans le type Duck, car la chose qui contrôle la création des dépendances n'est pas le ctor Duck, mais un certain contexte extérieur; ceci étant un exemple de jouet donné pour plus de clarté, il est parfaitement bien que le contexte extérieur soit représenté uniquement par le code appelant.
Filip Milovanović

Réponses:


21

Bien sûr, mais nous appelons cette composition et cette 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 lorsqu'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 à la 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 FlyBehaviorqui a une flyméthode 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. C'est la composition et la 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 croient 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épart, 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 d'arrêter 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. Privilégier 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. En outre, il n'y a aucun risque de problème yo-yo d' héritage 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'un bon nom.


1
" Ne sous-estimez jamais la valeur d'une bonne réputation ". Le nouveau temps des vêtements de l'empereur: LoginExceptionn'est pas un bon nom. C'est un "nom de schtroumpf" classique et, si je retire Exception, tout ce que j'ai Login, c'est qu'il ne me dit rien de ce qui s'est mal passé.
David Arno

Malheureusement, nous sommes coincés avec la convention ridicule de mettre Exceptionla fin de chaque classe d'exception, mais s'il vous plaît ne confondez pas «coincé avec» avec «bon».
David Arno

@DavidArno Si vous pouvez suggérer un meilleur nom, je suis tout ouïe. Ici, nous avons l'avantage de ne pas être pris au piège dans les conventions d'une base de code existante. Si vous aviez le pouvoir de changer le monde avec un claquement de doigts, comment le nommeriez-vous?
candied_orange

Je les nommer, StackOverflow, OutOfMemory, NullReferenceAccess, LoginFailureetc. Fondamentalement, prennent « Exception » hors du nom. Et, si nécessaire, corrigez-les afin qu'il décrive ce qui n'a pas fonctionné.
David Arno

@DavidArno par votre commande.
candied_orange

7

Vous pouvez remplacer presque n'importe quelle méthodologie par n'importe quelle autre méthodologie et continuer à produire des logiciels fonctionnels. 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érences personnelles 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 pour la correspondance claire avec le domaine problématique.


-1

L'héritage n'est pas aussi important qu'on le pensait autrefois. Il est toujours important et son retrait serait une mauvaise erreur.

Un exemple extrême est Objective-C où tout est une sous-classe de NSObject (le compilateur ne vous permet en fait pas de déclarer une classe qui n'a pas de classe de base, donc tout ce qui n'est pas construit dans le compilateur doit être une sous-classe de quelque chose intégré au compilateur). Il y a beaucoup de choses utiles intégrées à NSObject que vous ne devriez pas manquer.

Un autre exemple intéressant est UIView, la classe de base "view" dans le développement UIKit pour iOS. Il s'agit d'une classe qui est d'un côté un peu comme une classe abstraite déclarant une fonctionnalité que les sous-classes sont censées remplir, mais elle est également utile en elle-même. Il a des sous-classes fournies par UIKit, qui sont souvent utilisées telles quelles. Il y a composition par développeur installant des sous-vues dans une vue. Et il existe souvent des sous-classes définies par les développeurs, qui utilisent souvent la composition. Il n'y a pas de règle unique stricte ni même de règles, vous utilisez celle qui correspond le mieux à vos besoins.


Votre "exemple extrême" est à peu près le fonctionnement de la plupart des langages OO modernes: les sous type- classes Python , toutes les non primitives de Java héritent de Object, tout en JavaScript a un prototype se terminant par Object, etc.
Delioth

-1

[Je vais hasarder une réponse effrontée à la question titulaire.]

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

Oui ... sauf que le modèle de stratégie lui-même utilise l'héritage. Le modèle de stratégie fonctionne sur l'héritage d'interface. Remplacer l'héritage par composition + stratégie déplace l'héritage vers un autre endroit. Néanmoins, un tel remplacement vaut souvent le coup, car il permet de démêler les hiérarchies .


2
" Le modèle de stratégie fonctionne sur l'héritage d'interface ". N'oubliez pas que le modèle de stratégie est un modèle de conception; pas un modèle de mise en œuvre. Il peut donc être implémenté à l'aide d'interfaces, mais il peut également être implémenté à l'aide, par exemple, d'un hachage / dictionnaire de fonctions.
David Arno

3
L'héritage d'interface n'est pas du tout un héritage, c'est juste un peu un contrat ou un classificateur. Vous pouvez également utiliser des délégués pour le modèle de stratégie (je préfère les utiliser).
Deepak Mishra

Plus un, mec: ... travaille sur l'héritage d'interface , bravo pour une bonne utilisation du terme général "interface". Je pense qu'une conception de classe médiocre ou incompétente est la raison fondamentale pour laquelle cette question se pose. Pour expliquer pourquoi / comment prendrait un livre; Le diable est dans les détails. J'ai travaillé avec des conceptions d'héritage qui étaient une joie à voir et en même temps un héritage profond qui était l'enfer. J'ai vu un interfacedesign craptique corrigé avec l'héritage. Le problème caché ici est le comportement dépendant du morphing de l'implémentation. P, S. l'héritage et l'interface ne s'excluent pas mutuellement,
radarbob

Commentaire @DavidArno : Ce commentaire serait bien s'il était joint à la question OP. La question OP est techniquement mal formulée mais nous l'obtenons toujours. Le problème n'est pas cette réponse particulière.
radarbob

@DeepakMishra commentaire: . Une "interface" est tous les membres publics d'une classe. Malheureusement, le sens "interface" est surchargé. Nous devons être prudents pour distinguer la signification générale avec le motinterface
radarbob

-2

Non. Si un canard colvert a besoin de paramètres différents pour instancier qu'un autre type de canard, ce serait un cauchemar de changer. Devriez-vous également pouvoir instancier un canard? C'est plus un canard colvert ou un canard mandarin ou tout autre type de canards que vous avez.

Aussi, je pourrais vouloir une certaine logique autour des types afin qu'une hiérarchie de types soit meilleure.

Maintenant, si la réutilisation du code devient problématique pour certaines fonctionnalités, nous pourrions la composer en passant les fonctions via le constructeur.

Encore une fois, cela dépend vraiment de votre cas d'utilisation. Si vous avez juste un canard et un canard colvert, une hiérarchie de classes est une solution beaucoup plus simple.

Mais voici un exemple où vous souhaitez utiliser le modèle de stratégie: si vous avez des classes de clients et que vous souhaitez transmettre une stratégie de facturation (interface) qui pourrait être une stratégie de facturation happy hour ou une stratégie de facturation régulière, vous pouvez la passer chez le constructeur. De cette façon, vous n'avez pas à créer deux classes de clients différentes et vous n'avez pas besoin d'une hiérarchie. Vous n'avez qu'une seule classe de clients.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.