29 Mai 2015

DOJO #18

Petite nouveauté sur le Dojo cette semaine avec l’introduction du thème “Patterns & practices” !

Sujet : (re)découverte du pattern Command.

Déroulement

Afin de ne pas dévoiler l’intrigue dès le début, nous avons commencé par faire un petit exercice de traitement matriciel.
Il s’agissait dans un premier temps de créer une classe Point (exposant 3 propriétés de couleurs : Rouge, Vert, Bleu) et une classe Matrice contenant un ensemble de points. Ceci nous permettra de faire du traitement d’image de manière simplifiée.
La dessus nous avons implémenté un algorithme d’inversion des couleurs.
L’inversion des couleurs d’un point se fait très facilement de la manière suivante :

Une fois le traitement effectué sur l’ensemble de la matrice nous avons alors demandé aux participants quel pattern permettrait de :

  • Gérer l’annulation d’un traitement et sa réexécution (undo / redo)
  • Historiser un traitement
  • Empiler des requêtes pour les traiter ultérieurement

Au delà du test de connaissance, cette question avait pour but de mettre en avant les avantages d’un pattern qui, la plupart du temps est sous utilisé.
En effet, on a tendance à voir dans le pattern Command un moyen simple de forcer les développeurs à ne pas mettre de métier dans les couches hautes.
Exemple type dans un projet web MVC : une action = une commande !
Mais cette vision simpliste a deux défauts : Elle fait oublier les avantages précédemment listées de ce pattern (annulation, historisation,…) et on a tendance à coder tout le métier dans la commande, ce qui n’est pas préconisé.

Le schéma suivant illustre les bases du concept :
schema standard
Command :
Classe abstraite qui définit le comportement pour exécuter une opération.

ConcreteCommand :
Elle hérite de Command, et fait le lien entre un objet Receiver et une action.
Cet objet doit implémenter la méthode Execute et invoquer les actions adéquats sur le Receiver.
Elle porte également l’ensemble des variables nécessaires au traitement.

Client :
Il crée l’objet ConcreteCommand et lui fournit le Receiver nécessaire. Cela peut être une application console, un test unitaire, une action MVC, …

Invoker :
Il demande à la commande de traiter la requête. Il n’a aucune connaissance de ce que fait une commande, mais c’est lui qui décide quand doit être appelée la méthode Execute(). Il peut si besoin le faire de manière asynchrone, et pourquoi pas historiser les commandes exécutées, voir les annuler (rollback / undo).

Receiver :
C’est lui qui porte le traitement “métier” associé au traitement de la requête.

Voyons maintenant ceci sous l’angle de notre traitement matriciel.

Nous proposons une implémentation illustrée par le schéma suivant :
schema dojo

La classe Program correspond à l’application console qui liste les actions possibles (charger une matrice à partir d’une image, faire une rotation, inverser les couleurs, …) et demande à l’utilisateur de choisir une action à réaliser.

CommandProcessor a pour but d’exécuter les commandes et de les historiser.

MatrixProcessor s’occupe des différentes opérations matricielles : inversion des couleurs, rotation, etc.

Et sur la droite on retrouve la classe de base des commandes et une implémentation : RotateCommand.

Une petite différence à noter ici, l’instanciation des commandes est en fait réalisée dans une repository de commandes et non dans la classe Program, mais ceci ne modifie pas la ligne directrice du pattern.

Les technologies utilisées par les participants

  • C#
  • Javascript

Conclusion

Les participants ont eu parfois du mal à cerner l’intérêt d’un tel pattern, ou à imaginer dans quelles situations celui-ci pourrait nous être utile.

Il existe pourtant nombre de situations dans lesquelles celui-ci a des avantages. Il serait impossible d’en faire la liste exhaustive, mais on peut citer par exemple :

  • Empiler des traitements pour les traiter en asynchrone lorsqu’il n’y a pas de besoin d’avoir une réponse immédiate (envoi d’un mail de confirmation, traitement batch, …)
  • Gérer l’annulation d’une modification ou d’un traitement.
    • Pensez à gmail : lorsque l’on envoie un mail, un petit lien apparait en haut pendant quelques secondes pour annuler l’envoi => undo !
    • Imaginons une éditeur en ligne : chaque action peut être historisée et annulée à loisir.

Bien que cela n’apparaisse pas évident sur les schémas précédent, ce pattern permet également de rajouter du comportement “technique” en amont ou en aval du déclenchement de la commande. L’exemple ci-dessous met cet aspect en évidence via l’ajout d’un log technique juste avant et juste après l’invocation de la méthode Execute() :

Un pattern, rappelons le, n’a pas vocation à répondre à toutes les situations. Il sert à résoudre élégamment et intelligemment une problématique particulière. Il doit à ce titre être utilisé lorsque c’est nécessaire, pas pour le plaisir !

Share