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.
design-patterns
dependency-injection
strategy-pattern
Deepak Mishra
la source
la source
Duckbehavior.quackBehavior
et les autres champs de votre code?Réponses:
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
FlyBehavior
qui a unefly
mé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é :
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.
la source
LoginException
n'est pas un bon nom. C'est un "nom de schtroumpf" classique et, si je retireException
, tout ce que j'aiLogin
, c'est qu'il ne me dit rien de ce qui s'est mal passé.Exception
la fin de chaque classe d'exception, mais s'il vous plaît ne confondez pas «coincé avec» avec «bon».StackOverflow
,OutOfMemory
,NullReferenceAccess
,LoginFailure
etc. Fondamentalement, prennent « Exception » hors du nom. Et, si nécessaire, corrigez-les afin qu'il décrive ce qui n'a pas fonctionné.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.
la source
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.
la source
type
- classes Python , toutes les non primitives de Java héritent deObject
, tout en JavaScript a un prototype se terminant parObject
, etc.[Je vais hasarder une réponse effrontée à la question titulaire.]
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 .
la source
interface
design 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,interface
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.
la source