September 2, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

Working With Design Patterns: Memento

  • January 10, 2008
  • By Jeff Langr
  • Send Email »
  • More Articles »

A good user interface allows an end user to take back, or undo, operations that they performed. Similarly, the user should be able to redo previously "undone" operations. Just about everyone is familiar with—and often completely dependent on—the ability to revert from undesired changes to a Word document.

Command: The Basis for Undo/Redo

There are many ways to support undo and redo, but the command pattern is the basis of a clean and solid solution. With the command pattern, you represent each operation as a unique object. As the user executes operations, corresponding command objects are stored in a master list. As long as each command can support being "undone," undo and redo is a simple matter of tracking back and forth through this command list.

Listing 1 shows the Command interface, which specifies that all Command implementations must implement execute, redo, and undo methods. Listing 2 contains code for a class, CommandStack, that manages the master list of commands.

Listing 1: The Command interface.

public interface Command {
   void execute();
   void undo();
   void redo();
}

Listing 2: CommandStack.

import java.util.*;

public class CommandStack {
   private LinkedList<Command> commandStack =
      new LinkedList<Command>();
   private LinkedList<Command> redoStack =
      new LinkedList<Command>();

   public void execute(Command command) {
      command.execute();
      commandStack.addFirst(command);
      redoStack.clear();
   }

   public void undo() {
      if (commandStack.isEmpty())
         return;
      Command command = commandStack.removeFirst();
      command.undo();
      redoStack.addFirst(command);
   }

   public void redo() {
      if (redoStack.isEmpty())
         return;
      Command command = redoStack.removeFirst();
      command.redo();
      commandStack.addFirst(command);
   }
}

CommandStack encapsulates two stacks—a stack of commands executed, and a stack of commands that have been undone. As a command is executed, it's placed on the command stack. The undo operation removes the last command object from this command stack, undoes it, and places it on the redo stack. The redo stack reverses the sequence, removing from the redo stack, redoing the operation, and adding it back to the command stack (so that it can be undone again if necessary).

Note: Executing a command triggers the clearing of the redo stack. After executing a command, it may no longer make sense to redo a command that was previously undone. This mirrors how a user expects redo behavior to work and avoids otherwise confusing behavior.

The Memento

A memento represents something you want to hold onto (and no, it's not a chewy candy). In software design, the memento pattern provides a preferred way to hold onto information. Memento of course has other uses, but it's often applied as part of a solution to implementing undo and redo.

To support undo, an application must be able to revert to the state in which it existed before a user executed an operation. There are at least a couple ways you can implement a command's revert operation. If you can directly reverse an operation, undo is a simple matter of storing a list of executed operations, each associated with a reversal operation. For example, in a very simple calculator that supports addition and subtraction, you can revert to a prior value by reversing the operation. If a user added 5 to an initial value of 3, undo is a simple matter of subtracting the same quantity (5) from the current value of 8.

Sometimes, however, the prior state must be stored. Perhaps reversing an operation is too complex or not even feasible. As an example, consider a drawing application where random objects are drawn upon a screen. The paint portion of such a user interface must maintain a complete list of all objects to be drawn. Instead of trying to undraw a single object, the simpler approach is to store the complete list of objects prior to executing each command. This stored list is the memento. The memento is kept along with the associated command object.

Listing 3 shows ColoredPath, a model class that stores the coordinates for a polygon and its color. The PathCanvas class (see Listing 4) is the model class representing the composite drawing that contains all of the colored polygons.

Listing 3: ColoredPath.

import java.awt.*;
import java.awt.geom.*;

public class ColoredPath {
   private GeneralPath path;
   private Color color;

   public ColoredPath(GeneralPath path, Color color) {
      this.path = path;
      this.color = color;
   }

   public GeneralPath getGeneralPath() {
      return path;
   }

   public Color getColor() {
      return color;
   }
}

The most relevant portion of PathCanvas is its implementation of the MementoOriginator interface. When you request execution of a command that alters the PathCanvas, that command first must be able to ask for the current state of the PathCanvas. The PathCanvas originates this current state by creating a Memento object in the getMemento method. For the PathCanvas, the Memento is simply a copied collection of the ColoredPath objects (see Listing 5).





Page 1 of 3



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel