Making Commands Undoable and Keeping Command Histories


One of the optional yet powerful features of a command object is that it can enable undoable actions. You'll remember that we've defined the IUndoableCommand interface with an undo() method earlier in this chapter If we have our command classes implement IUndoableCommand, we can define an undo() method.

The undo() method can be simple or complex, depending on the complexity of the operation in the execute() method and the amount of state (how many properties) that must be remembered. Consider the simplest case in which the operation can be undone by simply negating the statement in the execute() method. For example, the execute() method of the RotateClockwiseCommand class from the previous section increments the rotation property of the receiver object by 20. Therefore, the undo() method ought to decrement the rotation property by 20. The following code defines the undo() method for an undoable version of the RotateClockwiseCommand class:

public function undo():void {    _receiver.rotation -= 20; }


The RotateClockwiseCommand example is fairly straightforward. If the object was rotated 20 degrees clockwise, the operation is clearly undone by rotating the object counterclockwise by 20 degrees. There is no additional state that the command class has to track. However, consider an example with more complex state options. For example, a RandomMoveCommand class can move an object to random coordinates. (For simplicity, we'll define the class so it always selects coordinates within the range defined by a 400-by-400 rectangle with the upper-left corner at 0,0.)

public class RandomMoveCommand implements ICommand {    private var _receiver:DisplayObject;    public function RandomMoveCommand(receiver:DisplayObject) {       _receiver = receiver;    }    public function execute():void {       var x:Number = Math.random() * 400;       var y:Number = Math.random() * 400;       _receiver.x = x;       _receiver.y = y;    } }


In the RandomMoveCommand example, the command object needs to track the previous x and y coordinates of the object in order to implement an undo() method. The following code shows how to implement an undoable version of the RandomMoveCommand class:

public class RandomMoveCommand implements IUndoableCommand {    private var _receiver:DisplayObject;    private var _x:Number;    private var _y:Number;    public function RandomMoveCommand(receiver:DisplayObject) {       _receiver = receiver;    }    public function execute():void {       _x = _object.x;       _y = _object.y;       var x:Number = Math.random() * 400;       var y:Number = Math.random() * 400;       _receiver.x = x;       _receiver.y = y;    }    public function undo():void {       _receiver.x = _x;       _receiver.y = _y;    } }


Of course, by itself, an undoable command isn't of much use. You can always store a reference to the most recent command, and that way you can add one level of undo to an application. However, most frequently you'll want to have more than one level of undo in an application. To accomplish that, you'll need to keep track of the command history.

Keeping track of command history isn't difficult. It requires an array and a cursor you can move to point to a specific command in the history. For that purpose, it's useful to define a CommandStack class. For the CommandStack class, we'll assume that you always want to keep track of command history globally within an application, and we'll therefore write the class as a Singleton class (described in Chapter 4, "Singleton Pattern"). If you wanted to keep track of command histories within unique areas of an application, you could change the implementation of the CommandStack class slightly. The following is the CommandStack class defined in the com.peachpit.aas3wdp.commands package:

package com.peachpit.aas3wdp.commands {    import com.peachpit.aas3wdp.commands.ICommand;    public class CommandStack {       private static var _instance:CommandStack;       private var _commands:Array;       private var _index:uint;       public function CommandStack(parameter:SingletonEnforcer) {          _commands = new Array();          _index = 0;       }       public static function getInstance():CommandStack {          if(_instance == null) {             _instance = new CommandStack(new SingletonEnforcer());          }          return _instance;       }             public function putCommand(command:ICommand):void {          _commands[_index++] = command;          _commands.splice(_index, _commands.length - _index);       }       public function previous():ICommand {          return _commands[--_index];       }             public function next():ICommand {          return _commands[_index++];       }       public function hasPreviousCommands():Boolean {          return _index > 0;       }       public function hasNextCommands():Boolean {          return _index < _commands.length;       }      } } class SingletonEnforcer {}





Advanced ActionScript 3 with Design Patterns
Advanced ActionScript 3 with Design Patterns
ISBN: 0321426568
EAN: 2147483647
Year: 2004
Pages: 132

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net