Avantages du modèle de stratégie

15

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?

Armon Safai
la source
2
Est-ce des devoirs? Il est préférable de le préciser dès le départ.
Fuhrmanator du
2
@Fuhrmanator no it isnt
Armon Safai

Réponses:

20

D'une part, les gros if/elseblocs 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/elsevous 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:

  • Vous définissez une interface, par exemple TaxCalculation, et votre framework accepte des instances de ce type pour calculer les taxes
  • Un utilisateur du framework crée une classe qui implémente cette interface et la transmet à votre framework, fournissant ainsi un moyen d'effectuer une partie des calculs

Vous 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, les if/elseblocs 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 3 if/elseblocs 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" ).

scriptin
la source
1
if/elsebloque également la lisibilité du code. Quant au modèle de stratégie, le principe Open / Closed mérite d'être mentionné IMO.
Maciej Chałapuk
1
La testabilité est la grande raison. (La plupart) Chaque branche de votre code doit être testée. Plus ifvous 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 nombreuses ifdé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.
Greg Burghardt
Merci pour la réponse. Alors, pourquoi ne puis-je pas utiliser des méthodes distinctes pour différents algorithmes dans la même classe?
Armon Safai
Vous pouvez, mais vous aurez toujours besoin d'un tas de if/elseblocs 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.
scriptin
1
Pouvez-vous être plus clair sur les raisons pour lesquelles il est plus facile de tester? L'exemple de refactorisation d'une déclaration de cas (ou si / alors) en une méthode polymorphe (base de la stratégie) est assez facile à tester. refactoring.com/catalog/replaceConditionalWithPolymorphism.html Si je connais toutes les conditions à tester, j'écris un test pour chacune. Si j'ai des stratégies, je dois instancier et exécuter une pour chacune. Comment l'approche stratégique est-elle plus facile à tester? Nous ne parlons pas de si complexes imbriqués lorsque vous refactorisez la stratégie.
Fuhrmanator
5

Pourquoi est-il avantageux d'utiliser le modèle de stratégie si vous pouvez simplement écrire votre code dans les cas if / then?

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.

Telastyn
la source
qu'est-ce qui ne va pas avec la modification du code dans le code if / then? ne devriez-vous pas également modifier le code dans le modèle de stratégie si vous décidez de changer le fonctionnement de l'un des algorithmes?
Armon Safai
@armonsafai - si vous modifiez la stratégie, il vous suffit de tester la stratégie. Si vous modifiez tous les algorithmes, vous devez tester tous les algorithmes. Pire, si vous ajoutez une nouvelle stratégie, il vous suffit de tester la stratégie. Si vous ajoutez un nouveau conditionnel, vous devez tester tous les conditionnels.
Telastyn
4

La stratégie est utile lorsque les if/thenconditions sont basées sur des types , comme expliqué dans http://www.refactoring.com/catalog/replaceConditionalWithPolymorphism.html

Les 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:

Utilisez le modèle de stratégie lorsque

...

  • une classe définit de nombreux comportements, et ceux-ci apparaissent comme plusieurs instructions conditionnelles dans ses opérations. Au lieu de nombreuses conditions, déplacez les branches conditionnelles associées dans leur propre classe de stratégie.

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/thenconditionnelles, 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.

Fuhrmanator
la source
3

[...] si vous pouvez simplement écrire votre code dans les cas if / then?

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ée trueest différent de la partie dans laquelle la décision est évaluée false.


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ù le idpourrait ê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 getByIdméthode et cette modification se reflète partout dans le code où vous l'appelez.

Avec if/ elseif/ elsec'est impossible à faire. En ajoutant une nouvelle implémentation, vous devez ajouter un nouveau elseifbloc 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.


De plus, qu'est-ce que cela signifie pour l'algorithme de changer au moment de l'exécution?

Dans mon exemple, le idpourrait être une variable qui est remplie en fonction d'une entrée utilisateur. Si l'utilisateur clique sur un bouton A, alors id = 2, s'il clique sur un bouton B, alors id = 8.

En raison de la idvaleur différente , une implémentation différente d'une interface est obtenue à partir du conteneur IoC et le code effectue différentes opérations.

Andy
la source
Merci pour la réponse. Alors, pourquoi ne puis-je pas utiliser des méthodes distinctes pour différents algorithmes dans la même classe?
Armon Safai
@ArmonSafai Des méthodes séparées résoudraient-elles vraiment quelque chose? Je ne pense pas. Vous déplacez le problème d'un endroit à un autre, et la décision concernant la méthode à appeler sera prise en fonction du résultat d'une condition. Encore une fois, un état if/ elseif/ else. Comme avant, juste dans un endroit différent.
Andy
Donc, les cas si / alors seraient en gros à droite? Ne seriez-vous pas obligé d'utiliser également les cas if / then pour le modèle de stratégie?
Armon Safai
1
@ArmonSafai Non, vous ne le feriez pas. Vous auriez un commutateur sur la idvariable dans la getByIdmé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.
Andy
1
@ArmonSafai Vous pourriez tout aussi bien avoir une méthode getSortByEnumType(SortEnum type)renvoyant une implémentation d'une Sortinterface, avoir une méthode getSortTyperenvoyant une SortEnumvariable et prenant une collection en paramètre, et la getSortByEnumTypeméthode contiendrait à nouveau un interrupteur sur le typeparamè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.
Andy
2

Pourquoi est-il avantageux d'utiliser le modèle de stratégie si vous pouvez simplement écrire votre code dans les cas if / then?

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.

De plus, qu'est-ce que cela signifie pour l'algorithme de changer au moment de l'exécution?

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.

Alain O'Dea
la source
1

Il n'y a rien de mal en if/elsesoi. Dans de nombreux cas, if/elsec'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:

  • Si les algorithmes de calcul de taxe particuliers peuvent changer indépendamment les uns des autres et de la logique de base. Dans ce cas, il serait intéressant de les séparer en classes distinctes, car les modifications seront localisées.
  • Si de nouveaux algorithmes peuvent être ajoutés à l'avenir, sans que la logique de base ne change.
  • Si la cause de la différence entre les deux algorithmes affecte également d'autres parties du code. Supposons que vous sélectionniez entre les deux algorithmes en fonction de la tranche de revenu du contribuable. Si cette tranche de revenu vous fait également sélectionner différentes branches à d'autres endroits du code, il est plus propre d'instancier une stratégie correspondant à la tranche de revenu une fois, et d'appeler si nécessaire, plutôt que d'avoir plusieurs branches if / else dispersées sur le code.

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.

JacquesB
la source