The command pattern is a behavioural pattern that encapsulates a request made to a certain object inside a Command object. Each type of command knows how to Execute() itself, as well as its target instances.
In the example above, taken from the Gang of Four’s Design Patterns, a PasteCommand would be associated to the Paste menu item. When the item is clicked, the command is executed, and, having a reference to the Document, calls Paste() on it.
This pattern is useful when you want to defer the execution of a certain command, for example, or store a log for a given transaction to be able to recover from a crash. It also allows you to define a high-level language for operations within your system.
However, the motivation for it in this case is to be able to support undo/redo operations. This requires a number of additions to the standard Command pattern.
First of all, the abstract Command class must implement Undo() and Redo() methods, aside from Execute(). It will also probably need an old state object, in order to know how to go back to the previous state before its execution. Therefore, having OldState and NewState fields in each command is a good practice to give the command all the information it needs for execution. The Sender of the command mayalso be required.
To handle these commands, a global History object is necessary. This object has a collection of listeners that implement a certain interface (in the example, the Document), and methods to Execute a command, Undo and Redo.
The mechanics would be the following: at the beginning of the application all listeners subscribe to the History class. When an option is selected, a Command is created with current and next state, and sent to the history for execution (note that current state can be obtained from a State provider).
History then executes the command for each of its listeners, which will decide whether to take action based on the command type and parameters. And then it stores the command inside a list for undoing and redoing.
When the history receives an Undo message, it looks for the last executed command and calls its Undo() method. In order to support multiple undo operations, the history must keep a current command index.
Grouping commands requires special attention. Sometimes a single action may trigger more than one command (for example, clicking on a link may involve OpenBrowserCommand and NavigateToCommand). However, if the user undoes and redoes this action, the browser will first open in an empty browser and only navigate in the next redo, exposing the nature of the commands to the user.
In order to avoid this, one possibility is to have a MacroCommand object that contains multiple commands. Whenever it is executed, it invokes the Execute() method of all of its children, same as Undo() and Redo().
The problem with this is that a single action may involve several components. For example, when a browser is created, it may decide on its own to launch a NavigateToCommand. We can’t group this with the previous command as it depends on its execution.
This is when timing commands comes in handy. By adding a timestamp to each command, the undo and redo operation can be done by timespans. Therefore, when the History receives an Undo message, we don’t undo the last command, but the commands executed in the last second.
Dealing with time is always problematic. A command that takes too long to execute may exceed this threshold, and a fast-clicking user may trigger two different actions within the timespan, and be undone together. Tuning this threshold is more difficult and error prone than the MacroCommand alternative, yet much more flexible.
The usage of commands in a web application is also good for logging. Whenever a command is issued, logging its parameters and states allow you to keep a trace on the user actions. With appropriate analysis tools, it can allow you to extract metrics on actual user reactions upon the interface. Knowing that all users entering the “Configuration” page are leaving without altering its content could mean that the page is not very intuitive (if not scary).
It also allows you to inject different aspects into the system, such as providing help based on the user actions, which will hopefully be the subject of the next post.
Update: As Martin pointed out, GWT provides a History object that allows you to manage the browser’s back and forward buttons, by storing states (commands) within the browser’s history stack and responding to changes via listeners.
Este si me gustó. Se me cruzó por la cabeza algo que nunca vi en ningún programa: Seria un experimento interesante dada la history de 10 commands (en algo más del estilo photoshop) decir “borro el 5to”. Y que eso haga los UNDOs y REDOs necesarios. Supongo que sería posible siempre y cuando se ignoren los comandos subsiguientes que hagan referencia a objetos creados por el comando que se borró. Me parece un feature interesante para ver que sale.
Esta buena la idea. Creo que seria mas claro (para usuario e implementador) algo asi como tener una lista global de undo/redo como hay ahora, y a su vez sublistas por cada “componente”.
Ejemplo, si agregas un cuadrado, se te genera una lista de historia nueva que tiene cada cosa que le haces al cuadrado (cambiar tamaño, color, etc) y que podes deshacer/rehacer independientemente de los demas componentes.
Me gusto, me gusto. Adicion para paint.net?
Para hacer eso existe un patrón que se llama memento, o me equivoco?
me parece que eso les va a solucionar el tema de undo/redo.
Saludos
Memento vendria a ser el “State” que va asociado a cada Command. No es mas que un object que cada comando lo construye de la forma que necesita para ejecutarse cuando necesite.