Pourquoi est-il avantageux d'utiliser le modèle de stratégie si vous pouvez simplement écrire votre code dans des cas si/alors?
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?
D'une part, les gros blocs de blocs if/else
Ne sont pas difficilement 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 if/else
Imbriqué, 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 plateforme.
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 ex. USAIncomeTaxCalculator
, if/else
les blocs sont "sans nom", dans le meilleur des cas, juste commentés, et les commentaires peuvent mentir. De plus, à mon goût personnel, le simple fait d'avoir plus de 3 blocs if/else
D'affilée n'est pas lisible, et cela devient très mauvais avec les blocs imbriqués.
Le principe ouvert/fermé est également très pertinent, car, comme je l'ai décrit dans l'exemple ci-dessus, la stratégie vous permet d'étendre une logique dans certaines parties de votre code ("ouvert pour extension") sans réécrire ces parties ("fermé pour modification").
Pourquoi est-il avantageux d'utiliser le modèle de stratégie si vous pouvez simplement écrire votre code dans des cas si/alors?
Parfois, vous devez simplement utiliser if/then. Il s'agit d'un code simple et facile à lire.
Les deux principaux problèmes avec le code simple if/then est qu'il peut violer le principe ouvert et fermé . 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 stratégie est utile lorsque le if/then
les conditions 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 ce qu'on appelle principe ouvert-fermé ou principe des variations protégées . 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 trivial).
Avec if/then
conditionnelles, il faut 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.
[...] 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 où une décision est évaluée en true
est différent de la partie où 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 méthode getById(int id)
, où le id
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 méthode getById
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 nouveau bloc elseif
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 à son structure.
De plus, qu'est-ce que cela signifie pour l'algorithme de changer au moment de l'exécution?
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, alors id = 2
, s'il clique sur un bouton B, alors id = 8
.
En raison de la valeur id
différente, une implémentation différente d'une interface est obtenue à partir du conteneur IoC et le code effectue différentes opérations.
Pourquoi est-il avantageux d'utiliser le modèle de stratégie si vous pouvez simplement écrire votre code dans des cas si/alors?
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, alors les 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 fusion 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 si/alors 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.
Il n'y a rien de mal avec if/else
en soi. Dans de nombreux cas if/else
est la façon 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 principale 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.