TP6.md 4.1 KB

Minipaint

TP6 – Le patron Command

Jusqu’ici, toutes les actions sont déclenchées à partir de clics sur des boutons et/ou sur le DrawingPane. Le code métier définissant chaque action est écrit dans les « handler » correspondants (ClearButtonHandler, RectangleButtonHandler, etc), ce qui n’est pas très élégant. Pour ajouter de la flexibilité à la façon dont les actions sont déclenchées, et aussi pour pouvoir implémenter un mécanisme de type undo/redo, nous allons nous intéresser au patron Command.

La vidéo de Christopher Okhravi est disponible ici 📺

Le principe de ce patron consiste à encapsuler les actions (ou « commandes ») dans des objets. En fait, cela revient à extraire le code métier des « handler », dans des objets indépendants : on sépare le code qui déclenche l’action du code de l’action elle-même.

Pour y parvenir, nous allons procéder en plusieurs étapes.

🕹 1. L’interface ICommand


🖥 TODO

  • Créer l’interface drawing.commands.ICommand, contenant deux méthodes sans paramètres execute() et undo().


🛍 2. Création des commandes


Pour chaque « handler » correspondant à une action, nous allons extraire le code de l’action et en faire une commande. Cette transformation peut être schématisée par le diagramme suivant (commencez par le ClearButtonHandler, c’est le plus facile) :

Diagramme Command

Pour cela, procédez comme suit :

🖥 TODO

  • Dans le handler, identifiez le code correspondant à l’action à effectuer, et le « récepteur » (receiver) de l’action (ce sur quoi l’action agit).
  • Créez une classe correspondant à cette action, qui implémente l’interface ICommand et qui contient un attribut correspondant au récepteur de l’action, ainsi que le code de l’action proprement dite (dans la méthode execute).
  • Implémentez la méthode undo (qui doit faire l’inverse de execute). Il faudra peut-être stocker quelques informations supplémentaires au moment du execute pour savoir quoi « défaire » lors du undo.
  • Dans le handler, ajoutez un attribut de type ICommand. Sa valeur sera récupérée au moment de l’instantiation du handler.
  • Dans le code du handler, remplacez le code de l’action par un appel à la méthode execute de la commande précédemment créée.
  • Vérifiez que l’action fonctionne toujours !


⏮ 3. Undo


Puisque chaque commande sait annuler son action (grâce à la méthode undo), il suffit maintenant de se rappeler la liste des commandes exécutées pour être capable de les annuler les unes après les autres.

🖥 TODO

  • Pour cela, créez une classe drawing.commands.CommandHistory, qui contiendra une pile de commandes. Elle proposera également 2 méthodes publiques : une méthode exec(ICommand) qui prend en paramètre une commande, qui l’ajoute à la pile de commandes et qui l’exécute ; et une méthode undo() qui enlève la dernière commande de la pile et qui demande son annulation.
  • La classe DrawingPane contiendra un attribut de type CommandHistory, qui sera rendu accessible grâce à un getter public.
  • Modifiez les handlers de telle sorte qu’ils n’appellent plus directement la méthode execute sur les commandes, mais plutôt qu’ils fassent appel à la méthode exec de la classe CommandHistory.
  • Ensuite, ajoutez un bouton dans la Toolbar qui permet d’annuler les actions (et qui fait donc appel à la méthode undo de la classe CommandHistory).


🐞 4. Debug


Enchainez les actions suivantes :

  • Créez un cercle
  • Effacez tout
  • Créez un rectangle
  • Effacez tout
  • Annulez 3 fois

🖥 TODO

  • Que constatez-vous ? D’après vous, où se situe le problème ?
  • Quelle solution proposez-vous pour corriger ce bug ?


🤖 5. Redo


🖥 TODO

  • Inspirez-vous de ce que vous avez fait pour réaliser le undo afin d’implémenter la fonction « redo ».


🔙 Retour