Pourquoi est-il avantageux d'utiliser le modèle de stratégie si vous pouvez simplement écrire votre code dans les cas if / then?
Par exemple: j'ai une classe TaxPayer, et l'une de ses méthodes calcule les taxes en utilisant différents algorithmes. Alors pourquoi ne peut-il pas avoir de cas if / then et déterminer quel algorithme utiliser dans cette méthode, au lieu d'utiliser le modèle de stratégie? Aussi, pourquoi ne pouvez-vous pas simplement implémenter une méthode distincte pour chaque algorithme de la classe TaxPayer?
De plus, qu'est-ce que cela signifie pour l'algorithme de changer au moment de l'exécution?
Réponses:
D'une part, les gros
if/else
blocs de blocs ne sont pas facilement testables . Chaque nouvelle "branche" ajoute un autre chemin d'exécution et augmente ainsi la complexité cyclomatique . Si vous voulez tester votre code à fond, vous devez couvrir tous les chemins d'exécution, et chaque condition vous obligerait à écrire au moins un test supplémentaire (en supposant que vous écriviez de petits tests ciblés). D'un autre côté, les classes qui implémentent des stratégies exposent généralement une seule méthode publique, ce qui est facile à tester.Ainsi, avec imbriqué,
if/else
vous vous retrouverez avec de nombreux tests pour une seule partie de votre code, tandis qu'avec Strategy, vous aurez peu de tests pour chacune des multiples stratégies plus simples. Avec ce dernier, il est facile d'avoir une meilleure couverture, car il est plus difficile de manquer les chemins d'exécution.En ce qui concerne l' extensibilité , imaginez que vous écrivez un framework, où les utilisateurs sont censés pouvoir injecter leur propre comportement. Par exemple, vous souhaitez créer une sorte de cadre de calcul fiscal et prendre en charge les systèmes fiscaux de différents pays. Au lieu de les implémenter tous, vous voulez simplement donner aux utilisateurs du cadre une chance de fournir une implémentation de la façon de calculer certaines taxes particulières.
Voici le modèle de stratégie:
TaxCalculation
, et votre framework accepte des instances de ce type pour calculer les taxesVous ne pouvez pas faire de même avec
if/else
, car cela nécessiterait de changer le code du framework, auquel cas ce ne serait plus un framework. Étant donné que les cadres sont souvent distribués sous forme compilée, cela peut être la seule option.Pourtant, même si vous écrivez du code normal, la stratégie est bénéfique car elle rend vos intentions plus claires. Il indique que "cette logique est enfichable et conditionnelle", c'est-à-dire qu'il peut y avoir plusieurs implémentations qui peuvent varier en fonction des actions de l'utilisateur, de la configuration ou même de la plate-forme.
L'utilisation du modèle de stratégie peut améliorer la lisibilité car, alors qu'une classe qui implémente une stratégie particulière devrait généralement avoir un nom descriptif, par exemple
USAIncomeTaxCalculator
, lesif/else
blocs sont "sans nom", dans le meilleur des cas, simplement commentés, et les commentaires peuvent mentir. De plus, à mon goût personnel, le simple fait d'avoir plus de 3if/else
blocs d'affilée n'est pas lisible et cela devient assez mauvais avec les blocs imbriqués.Le principe Open / Closed est également très pertinent, car, comme je l'ai décrit dans l'exemple ci-dessus, Strategy vous permet d'étendre une logique dans certaines parties de votre code ("ouvert pour extension") sans réécrire ces parties ("fermé pour modification" ).
la source
if/else
bloque également la lisibilité du code. Quant au modèle de stratégie, le principe Open / Closed mérite d'être mentionné IMO.if
vous en avez, plus il y a de chemins possibles dans votre code, plus vous devez écrire de tests et plus de façons d'échouer pour cette méthode. Si je peux citer le regretté Yogi Berra: "Si vous venez à une fourche sur la route, prenez-la." Cela s'applique brillamment aux tests unitaires. En outre, de nombreusesif
déclarations signifient que vous êtes susceptible de répéter la logique de ces conditions, augmentant encore votre charge de test et augmentant le risque de bogues.if/else
blocs pour les appeler (ou à l'intérieur, pour déterminer si cela doit faire quelque chose ou non), donc ce n'est pas d'une grande aide, sauf peut-être un code plus lisible. Et également aucune extensibilité pour les utilisateurs de votre cadre hypothétique.Parfois, vous devez simplement utiliser if / then. Il s'agit d'un code simple et facile à lire.
Les deux principaux problèmes avec un code if / then simple est qu'il peut violer principe d'ouverture et de fermeture . Si vous devez entrer et ajouter ou modifier une condition, vous modifiez ce code. Si vous vous attendez à avoir plus de conditions, l'ajout d'une nouvelle stratégie est plus simple / plus propre / moins susceptible de se casser.
L'autre problème est le couplage. En utilisant if / then, toutes les implémentations sont liées à cette implémentation, ce qui les rend plus difficiles à modifier à l'avenir. En utilisant la stratégie, le seul couplage est à l'interface de la stratégie.
la source
La stratégie est utile lorsque les
if/then
conditions sont basées sur des types , comme expliqué dans http://www.refactoring.com/catalog/replaceConditionalWithPolymorphism.htmlLes conditions de vérification de type n'ont généralement pas une complexité cyclomatique élevée, donc je ne dirais pas que la stratégie améliore nécessairement les choses.
La raison principale de la stratégie est expliquée dans le livre du GoF p.316 qui a introduit le modèle:
Comme mentionné dans d'autres réponses, s'il est appliqué de manière appropriée, le modèle de stratégie permet d'ajouter de nouvelles extensions (stratégies concrètes) sans nécessairement nécessiter de modifications du reste du code. C'est le principe dit Open-Closed ou Protected Variations . Bien sûr, vous devez toujours coder la nouvelle stratégie concrète, et le code client doit reconnaître les stratégies comme des plug-ins (ce n'est pas anodin).
Avec les
if/then
conditionnelles, il est nécessaire de changer le code de la classe contenant la logique conditionnelle. Comme mentionné dans d'autres réponses, cela est parfois OK lorsque vous ne voulez pas ajouter la complexité pour prendre en charge l'ajout de nouvelles fonctionnalités (plug-ins) sans recompiler.la source
C'est exactement le plus grand avantage du modèle stratégique. Ne pas avoir de conditions.
Vous voulez que vos classes / méthodes / fonctions soient aussi simples et courtes que possible. Le code court est très facile à tester et très facile à lire.
Les conditions (
if
/elseif
/else
) rendent vos classes / méthodes / fonctions longues, car généralement le code dans lequel une décision est évaluéetrue
est différent de la partie dans laquelle la décision est évaluéefalse
.Un autre grand avantage du modèle de stratégie est qu'il est réutilisable tout au long de votre projet.
Lorsque vous utilisez le modèle de conception de stratégie, vous avez très probablement une sorte de conteneur IoC, à partir duquel vous obtenez l'implémentation souhaitée d'une interface, peut-être par une
getById(int id)
méthode, où leid
pourrait être un membre énumérateur.Cela signifie que la création de l'implémentation ne se fait qu'à un seul endroit de votre code.
Si vous souhaitez ajouter d'autres implémentations, vous ajoutez la nouvelle implémentation à la
getById
méthode et cette modification se reflète partout dans le code où vous l'appelez.Avec
if
/elseif
/else
c'est impossible à faire. En ajoutant une nouvelle implémentation, vous devez ajouter un nouveauelseif
bloc et le faire partout où les implémentations ont été utilisées, ou vous pourriez vous retrouver avec un code qui n'est pas valide, car vous avez oublié d'ajouter l'implémentation à sa structure.Dans mon exemple, le
id
pourrait être une variable qui est remplie en fonction d'une entrée utilisateur. Si l'utilisateur clique sur un bouton A, alorsid = 2
, s'il clique sur un bouton B, alorsid = 8
.En raison de la
id
valeur différente , une implémentation différente d'une interface est obtenue à partir du conteneur IoC et le code effectue différentes opérations.la source
if
/elseif
/else
. Comme avant, juste dans un endroit différent.id
variable dans lagetById
méthode, qui retournerait l'implémentation spécifique. Chaque fois que vous auriez besoin d'une implémentation de l'interface, vous demanderiez au conteneur IoC de vous la livrer.getSortByEnumType(SortEnum type)
renvoyant une implémentation d'uneSort
interface, avoir une méthodegetSortType
renvoyant uneSortEnum
variable et prenant une collection en paramètre, et lagetSortByEnumType
méthode contiendrait à nouveau un interrupteur sur letype
paramètre vous renvoyant l'algorithme de tri correct. Si vous aviez besoin d'ajouter un nouvel algorithme de tri, il vous suffit de modifier l'énumération et une méthode. Et vous êtes prêt.Le modèle de stratégie vous permet de séparer vos algorithmes (les détails) de votre logique métier (politique de haut niveau). Ces deux choses sont non seulement déroutantes à lire lorsqu'elles sont mélangées, mais elles ont également des raisons très différentes de changer.
Il existe également un facteur d'évolutivité du travail d'équipe majeur ici. Imaginez une grande équipe de programmation où de nombreuses personnes travaillent sur ce progiciel de comptabilité. Si les algorithmes fiscaux sont tous dans la classe ou le module TaxPayer , des conflits de fusion deviennent probables. Les conflits de fusion prennent du temps et sont susceptibles d'être résolus. Cette perte de temps sape la productivité de l'équipe et les erreurs introduites par les mauvaises fusions nuisent à la crédibilité des clients.
Un algorithme qui change au moment de l'exécution est un algorithme dont le comportement est déterminé par la configuration ou le contexte. Une approche if / then en place ne permet pas efficacement cela car elle implique le rechargement des classes existantes activement utilisées. Avec le modèle de stratégie, les objets de stratégie implémentant chaque algorithme peuvent être construits lors de l'utilisation. Par conséquent, des modifications de ces algorithmes (corrections de bogues ou améliorations) pourraient être apportées et rechargées au moment de l'exécution. Cette approche pourrait être utilisée pour permettre une disponibilité continue et des versions sans interruption de service.
la source
Il n'y a rien de mal en
if/else
soi. Dans de nombreux cas,if/else
c'est la manière la plus simple et la plus lisible d'exprimer la logique. L'approche que vous décrivez est donc parfaitement valable dans de nombreux cas. (Il est également parfaitement testable, ce n'est donc pas un problème.)Mais il existe certains cas particuliers où un modèle de stratégie peut améliorer la maintenabilité du code global. Par exemple:
Pour que le modèle de stratégie ait un sens, l'interface entre la logique de base et les algorithmes de calcul de la taxe doit être plus stable que les composants individuels. S'il est tout aussi probable qu'un changement d'exigence entraîne un changement d'interface, le modèle de stratégie peut en fait constituer un handicap.
Tout se résume à la question de savoir si les «algorithmes de calcul de la taxe» peuvent être clairement séparés de la logique de base qui l'invoque. Un modèle de stratégie a des frais généraux par rapport à un
if/else
, vous devrez donc décider au cas par cas si l'investissement en vaut la peine.la source