Patron de conception: La stratégie
Les sources de cet article sont disponibles sur GitHub.
Présentation
Voici le troisième article de la série traitant des patrons de conception. Jusqu’à maintenant, nous avons abordé la fabrique, un patron de création, et le décorateur, un patron de structuration. Cet article-ci, quant à lui, traite d’un patron de comportement, soit: la stratégie.
La stratégie (Strategy Pattern)
La stratégie est un patron de conception que je n’ai pas vu utilisé régulièrement, mais qui est un des plus pratiques. Ce patron nous permet d’encapsuler un algorithme, la stratégie, dans un objet afin qu’il soit exécuté par l’appelant, le contexte. Ceci permet de créer des familles d’algorithmes qui peuvent être inter-changés à l’exécution. Clarifions…
Le problème
Vous devez implémenter des validations sur des objets. Par exemple, vous avez une classe User
et vous devez valider que l’utilisateur a plus de 18 ans. Rien de plus simple!
1 2 3 4 5 |
public void Save(User user) { if (user.Age < 18) throw new Exception("L'utilisateur doit être majeur"); userRepository.Save(user); } |
Ce cas-ci peut aller si vous n’avez pas plusieurs validations à appliquer. Ceci polluerait votre code et votre fonction Save(User user)
n’aurait plus qu’une seule responsabilité (SRP). Jusqu’ici, ce n’est toujours pas trop mal, mais qu’est-ce qui arrive si vous devez répéter ces validations dans une autre méthode? Vous allez dupliquer le code? Et qu’est-ce qui arrive si certaines validations sont optionnelles selon le contexte? Quelque chose comme ceci?
1 2 3 4 5 |
public void Save(User user) { if (ShouldApplyAgeValidation(user) && user.Age < 18) throw new Exception(); userRepository.Save(user); } |
À ce moment-ci, vos yeux devraient commencer à saigner abondamment.
La solution
Évidemment, la stratégie peut nous aider. Pour l’implémenter, il faut avoir un niveau d’abstraction pour les règles de validations. Ces règles seront les stratégies du patron de conception.
1 2 3 4 |
interface IValidationStrategy { void Validate(); } |
Dans l’exemple ci-dessus, toutes nos stratégies de validations auront une méthode pour appliquer les validations sur l’objet. Ensuite, nous allons créer une deuxième couche d’abstraction pour nous permettre d’encapsuler un objet à valider à l’aide des types génériques:
1 2 3 4 5 6 7 8 9 10 11 12 |
abstract class AbstractValidator<T> : IValidator { protected T ObjToValidate { get; set; } public AbstractValidator(T obj) { ObjToValidate = obj; } // Algorithme encapsulé public abstract void Validate(); } |
Rien de bien sorcier jusqu’à présent. Maintenant, il faut créer les stratégies concrètes:
1 2 3 4 5 6 7 8 9 10 11 12 |
class UserOver18Validator : AbstractValidationStrategy<User> { public UserOver18Validator(User obj) : base(obj) { } public override void Validate() { // Algorithme encapsulé if (this.ObjToValidate.Age < 18) throw new Exception("User must be over 18"); } } |
C’est ici que la stratégie prend tout son sens. À ce moment, vous avez une stratégie de validation encapsulée dans un objet qui n’a qu’un seul but: valider qu’un utilisateur est majeur.
La dernière partie du patron est le contexte appelant. Le contexte est une classe contenant une ou plusieurs stratégies à exécuter. Ici, nous l’appellerons le contexte de validation:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
class ValidationContext { private List<IValidationStrategy> Validators { get; set; } public ValidationContext() { Validators = new List<IValidationStrategy>(); } public ValidationContext AddValidationStrategy(IValidationStrategy validationStrategy) { Validators.Add(validationStrategy); return this; } public ValidationContext ApplyValidations() { this.Validators.ForEach(x => x.Validate()); return this; } } |
Donc ici, le contexte contient une liste de stratégies de validation à exécuter. À l’utilisation, nous ajouterons les stratégies au contexte, puis nous exécuterons les validations:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
static void Main(string[] args) { User someoneTooYoung = new User() { FirstName = "Steeve", Age = 14 }; ValidationContext context = new ValidationContext() .AddValidationStrategy(new UserOver18Validator(someoneTooYoung)) .ApplyValidations(); } |
Conclusion
En somme, la stratégie est un patron vraiment utile qui peut être implémenter facilement et rendre le code, l’algorithme, dynamique à l’exécution sans switch case
ou if
. Dans l’exemple ci-dessus, nous aurions pu obtenir l’instance de la stratégie de validation à l’aide du fabrique voire même un constructeur (builder) pour construire le contexte de validation!
N’hésitez pas à donner votre opinion dans les commentaires ci-dessous!
Code source
Les sources de cet article sont disponibles sur GitHub.
1 |
git clone http://github.com/EzoQC/StrategyPattern.git |
Commentaires
Laisser un commentaire