[Java][Design Pattern]Comment faire le undo/redo? Le principe de base !
par
, 05/11/2014 à 11h59 (4240 Affichages)
Bonjour,
Si vous êtes développeur, un jour ou l'autre, on va vous demander la fonction undo/redo. Si vous ne connaissez pas le pattern à utiliser à ce moment là, vous serez désespéré !
Pourtant le pattern est connu et "classique", c'est le pattern "Commande". Personnellement, je préfère le terme "Processeur de commande". Car, il met en évidence la présence d'un PROCESSEUR qui centralise (1).
Le problème du Undo/Redo, c'est la centralisation de l'information ! Il doit y avoir un et un seul endroit qui connait les commandes/actions qui ont été réalisées. Ce qu'on appel le "processeur".
Le processeur est relativement simple :
Il prendre UNE interface en entré (2) :
Code java : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7 package org.k.developpez.blog.undoredo; public interface Command { public void execute(); public void undo(); public boolean canBeUnDone(); }
Il a un historique (3) :
Code java : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4 // Historique des commandes réalisée private List<Command> historic = new ArrayList<Command>(); // Historique des commande annulée (le nom de la variable est foireux) private List<Command> unDoneHistoric = new ArrayList<Command>();
Les méthodes disponibles sur un processeur sont les suivantes :
Le plus compliqué étant de mettre à jour les listes d'historique à jour à chaque fois de manière approprié.
Code java : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 /** * Réalise la commande */ public void execute(Command command); /** * Annule la dernière commande exécute. */ public void undo(); /** * Execute la dernière commande annulée */ public void redo() { if(!unDoneHistoric.isEmpty()){ Command toRedo=unDoneHistoric.get(0); unDoneHistoric.remove(0); this.execute(toRedo); } else { System.out.println("Debug processor : No command to redo"); } } /** * Indique si on peut annuler la dernière commande * @return boolean */ public boolean canBeUnDone(); /** * Supprimme l'ensemble de l'historique */ public void reset();
Bien-sûr dans une application existante, il va falloir :
Crée le processeur.
Créer les commandes correspondantes à toutes les actions de l'applications.
Remplacer les actions par leur commande :
Code java : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8 //Anciennement Lamp lamp = new Lamp(); lamp.switch(); //nouvellement Lamp lamp = new Lamp(); SwitchCommand command = new SwitchCommand(lamp); Processor.getInstance().execute(command);
Ce qu'il faut retenir :
- Centralisé (Un processeur)
- Normalisé (Une interface **Command**)
- Mémorisé (un historique)
C'est les trois principes qui donnent une bonne implémentation du undo/redo
Code en action :
Dans un bût de démonstration, j'ai réalisé un processeur qui gère des commandes sur une lampe dont les sources sont en pièces joints. Ce n'est pas le plus avancé des Processeur de commande que j'ai réalisé, mais celui-ci réalise le undo et le redo de manière correcte.
Voici la trace commanté que donne mon exemple :
Cordialement,===== START TEST org.k.developpez.blog.SwitchCommand =====
Lamp is off
org.k.developpez.blog.SwitchCommand : Execute
Lamp is on
org.k.developpez.blog.SwitchCommand : undo
Lamp is off
org.k.developpez.blog.SwitchCommand : redo
Lamp is on
===== END TEST =====
===== START TEST org.k.developpez.blog.TurnOnCommand =====
Lamp is off
org.k.developpez.blog.TurnOnCommand : Execute
Lamp is on
org.k.developpez.blog.TurnOnCommand : undo // K : La commandeTurnOn passe à l'état on quelques soit l'état de la lampe avant. Il est donc impossible d'avoir la commande inverse ! On ne connais pas l'état d'avant (on ne la pas mémorisé).
Debug processor : No command to undo
Lamp is on
org.k.developpez.blog.TurnOnCommand : redo
Debug processor : No command to redo
Lamp is on
===== END TEST =====
===== START TEST org.k.developpez.blog.TurnOnCommand =====
Lamp is off
org.k.developpez.blog.TurnOnCommand : Execute
Lamp is on
org.k.developpez.blog.TurnOnCommand : undo // K : La commandeTurnOff passe à l'état off quelques soit l'état de la lampe avant. Il est donc impossible d'avoir la commande inverse ! On ne connais pas l'état d'avant (on ne la pas mémorisé).
Debug processor : No command to undo
Lamp is on
org.k.developpez.blog.TurnOnCommand : redo
Debug processor : No command to redo
Lamp is on
===== END TEST =====
Patrick Kolodziejczyk.
Source :
http://en.wikipedia.org/wiki/Command_pattern#Java
http://fr.wikipedia.org/wiki/Command..._conception%29