Design pattern : Pattern Strategy et Agilité
Pourquoi parler du pattern strategy? parce que c’est, a mon avis, le pattern que vous allez le plus souvent utiliser lorsque vous faites des projets agiles: qui dit projet Agile, dit projet ayant des tests unitaires, donc Injection de dépendance…et donc le pattern Strategy (dans BEAUCOUP de cas) !
C’est un pattern que j’aime beaucoup car il vous permet également de respecter deux des principes solides : SRP et OCP. De plus, c’est un pattern du GoF (de la catégorie comportementale) qui est extrêmement simple à comprendre et à mettre en place…ce qui n’est pas forcément toujours le cas.
A quoi sers stratégie ?
prenons la définition donnée par wikipedia :
En génie logiciel, le patron stratégie est un patron de conception (design pattern) de type comportemental grâce auquel des algorithmes peuvent être sélectionnés à la volée au cours du temps d'exécution selon certaines conditions, comme les stratégies utilisées en temps de guerre.
Le patron de conception stratégie est utile pour des situations où il est nécessaire de permuter dynamiquement les algorithmes utilisés dans une application. Le patron stratégie est prévu pour fournir le moyen de définir une famille d'algorithmes, encapsuler chacun d'eux en tant qu'objet, et les rendre interchangeables. Ce patron laisse les algorithmes changer indépendamment des clients qui les emploient.
On a donc la possibilité d’utiliser un traitement spécifique à un endroit donné du programme en fonction de nos besoins et du contexte.
Prenons un exemple concret :
J’ai ici une classe DataHandler qui doit charger des données et les écrire. Il y a donc deux traitements distincts sur lesquelles nous souhaitons utiliser le pattern strategy car dans un cas, je souhaite écrire dans la console, et dans l’autre dans un fichier.
Pour la partie « écriture », j’utilise donc une interface IDataWriter comme couche d’abstraction utilisée par la classe DataHandler. J’ai deux classes permettant de mettre en œuvre mes stratégies : FileWriter & ConsoleWriter.
public interface IDataWriter
{
void WriteData(List<string> messages);
}
public class ConsoleWriter:IDataWriter
{
public void WriteData(List<string> messages)
{
messages.ForEach(m => Console.WriteLine(m));
}
}
public class FileWriter : IDataWriter
{
private readonly string _fileName;
public FileWriter(string fileName )
{
_fileName = fileName;
}
public void WriteData(List<string> messages)
{
if (File.Exists(_fileName))
{
File.WriteAllLines(_fileName, messages);
}
}
}
J’utilise l’injection par constructeur dans la classe DataHandler pour appliquer la stratégie choisie dans mon traitement.
public class DataHandler
{
private readonly IDataWriter _dataWriter;
private readonly IDataProvider _dataProvider;
public DataHandler(IDataWriter dataWriter, IDataProvider dP)
{
_dataWriter = dataWriter;
_dataProvider = dP;
}
public void GetAndPrintMessages()
{
var messages = _dataProvider.GetMessages();
_dataWriter.WriteData(messages);
}
}
Lors de l’exécution de mon programme je n’ai qu’à construire mon objet DataHandler avec les bonnes stratégies et exécuter mon traitement.
var dh = new DataHandler(new ConsoleWriter(),
new DataProvider());
dh.GetAndPrintMessages();
Console.ReadKey();
Cet exemple montre bien que ce pattern est extrêmement simple, accessible par tous et que l’on peut l’utiliser pratiquement dans tous les cas de figure.
Pour résumer:
Vous remarquerez également que j’ai fait la même chose pour la partie chargement des données avec l’interface IDataProvider et la classe DataProvider et cela malgré le fait de n’avoir qu’une seul stratégie:
public interface IDataProvider
{
List<string> GetMessages();
}
public class DataProvider : IDataProvider
{
public List<string> GetMessages()
{
return new List<string>(){"a","b","c","d"};
}
}
C’est ici que le pattern strategy est réellement pratique dans vos développements Agile (avec beaucoup de refactoring en vue) :
· Respect du principe OCP : Si vous souhaitez ajouter une nouvelle stratégie, pas de problème : il vous suffit d’implémenter l’interface IDataProvider sans toucher à la classe DataHandler
· Respect du principe SRP : vous avez isolé la partie chargement du reste du traitement tout en vous assurant que les prochaines stratégies de chargement respecterons ce plan.
· Respect du principe DIP : vous n’avez pas de couplage entre la classe DataHandler & DataProvider…car vous faites de l’injection de dépendance.
Du coup, votre code est testable car facilement mockable !
Strategy est donc un pattern simple qui n’a que des avantages, vous n’avez donc aucune raisons de ne pas l’utiliser !
Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :