Chapter 11. Memento Pattern

<feature><title>In This Chapter</title> </feature>

The Memento pattern is a way of recording an object’s current state without breaking the rules of encapsulation. The rules of encapsulation say that an object should manage its own state, and that it should allow external objects to influence its state only through a well-defined API. For example, it is perfectly acceptable for a class to define a setter method that changes the value of a private property. However, it would be bad design to use public properties that can be set without going through a method of the class. Public properties allow external objects to change the object’s state without the object knowing what has occurred.

There are many reasons you might want to record an object’s state at a point in time. Frequently you want to record an object’s state so that you can return to that point if necessary. For example, an application with panel sets might enable the user to configure the panels by moving them and resizing them. You might then want to record the configuration so that the user can make changes but be able to return to the saved configuration. The difficulty is in how to record the state without breaking encapsulation. One option that might jump out immediately is to add methods that return each of the required values. For example, if you want to record a panel’s state, you might want to record the x and y coordinates as well as the width and height. That might seem simple enough. However, consider that an object’s internal state might be complex, and it might well be inappropriate to expose certain elements of the internal state in that way.

The Memento pattern elegantly solves this dilemma. The Memento pattern consists of three basic elements called the memento, the originator, and the caretaker. The originator is the class that needs to record a snapshot of its state. It accomplishes that by way of an instance of a memento class. The caretaker is the object that stores the memento until which time it needs to restore it to the originator. The originator class has an API that allows a caretaker class to request a memento object. The caretaker class stores the memento, and it then passes it back to the originator if requested.

The Memento pattern does not impose a very precise API that must be followed. However, the Memento pattern generally uses at least three classes for a basic implementation: the originator, the memento, and the caretaker.

The originator can be any sort of class for which you need to record the state at a point in time. The originator class must define methods to get and set the memento, which is used to save and restore state. The memento is usually tightly coupled with the originator. Because a memento records state for an originator, the memento must know about the type of state that the originator maintains. At this point, a very simple example will be helpful.

Consider the case of a Circle class like the following:

    package {
       import flash.display.Sprite;
       public class Circle extends Sprite {
          private var _radius:Number;
          private var _color:Number;

          public function set radius(value:Number):void {
             _radius = value;
             draw();
          }

          public function set color(value:Number):void {
             _color = value;
             draw();
          }

          public function Circle(radiusValue:Number, colorValue:Number) {
             _radius = radiusValue;
             _color = colorValue;
             draw();
          }

          private function draw():void {
             graphics.clear();
             graphics.lineStyle(1, _color, 1);
             graphics.beginFill(_color, 1)
             graphics.drawCircle(0, 0, _radius);
             graphics.endFill();
          }

       }

    }

This example is purposefully simple. This Circle class simply draws a circle. The only state it needs to maintain is the radius and the color with which to draw the circle. Here’s an example of code that creates a new Circle and adds it to the display list:

    var circle:Circle = new Circle(10, 0xFFFFFF);
    addChild(circle);

That code creates a white circle with a radius of 10. If you want, you can change the color to red and the radius to 20, like this:

     circle.color = 0xFF0000;
     circle.radius = 20;

But what happens if you then want to return to the previous state with the white color and the radius of 10? Clearly you must record the state before changing it so that you can restore it at a later time. The Memento pattern says that in order to record the state for the Circle class, we must create a memento type that we will call CircleMemento. The CircleMemento is capable of storing the radius and color values.

     package {
        public class CircleMemento {
           private var _radius:Number;
           private var _color:Number;
           public function get radius():Number {
              return _radius;
           }
           public function get color():Number {
              return _color;
           }
           public function CircleMemento(radiusValue:Number, colorValue:Number) {
              _radius = radiusValue;
              _color = colorValue;
           }

        }

     }

The memento class is a data-only class that simply stores all the values for the state that you want to record for a particular type. In the case of CircleMemento we want to record the radius and color values for a Circle instance.

Next, we need a way for the originator (Circle) to be responsible for saving and restoring its state. For that purpose, we add getMemento() and setMemento() methods to Circle.

     package {
        import flash.display.Sprite;
        public class Circle extends Sprite {
           private var _radius:Number;
           private var _color:Number;

           public function set radius(value:Number):void {
              _radius = value;
              draw();
           }

           public function set color(value:Number):void {
              _color = value;
              draw();
           }

           public function Circle(radiusValue:Number, colorValue:Number) {
              _radius = radiusValue;
              _color = colorValue;
              draw();
           }

           private function draw():void {
              graphics.clear();
              graphics.lineStyle(1, _color, 1);
              graphics.beginFill(_color, 1)
              graphics.drawCircle(0, 0, _radius);
              graphics.endFill();
           }

           public function getMemento():CircleMemento {
              return new CircleMemento(_radius, _color);

           }

           public function setMemento(memento:CircleMemento):void {

              _radius = memento.radius;
              _color = memento.color;
              draw();

           }
        
        }

     }

You can see that the getMemento() method constructs and returns a new CircleMemento object that stores the current state. The setMemento() method accepts a CircleMemento parameter and then restores the Circle object’s state to the state values from the memento.

The only object we haven’t yet looked at is the caretaker. The caretaker is the object that calls getMemento() to retrieve and store the current memento, and it then can pass that memento back to the object using setMemento(). In this case, the caretaker is whatever object is constructing the Circle instance. Here’s an example that creates a Circle instance: Every time the user clicks the circle, it changes the state randomly. The caretaker also records the current state by retrieving a memento from the Circle instance. Then the user can use the right and left keys on the keyboard to move backward and forward through the sequence of state changes.

     package {
        import flash.display.Sprite;
        import flash.events.KeyboardEvent;
        import flash.events.MouseEvent;
        import flash.ui.Keyboard;

        public class MementoExample extends Sprite {

           private var _circle:Circle;
           private var _previousMementos:Array;
           private var _nextMementos:Array;

           public function MementoExample() {
           // Create arrays to store the next and previous states.
           _previousMementos = new Array();
           _nextMementos = new Array();

           // Create a circle.
           _circle = new Circle(10, 0xFFFFFF);
           addChild(_circle);

           // Listen for click events on the circle. Listen for
           // keyboard events globally.
           _circle.addEventListener(MouseEvent.CLICK, onClick);
           stage.addEventListener(KeyboardEvent.KEY_UP, onKey);
         }

         // When thye user clicks on the circle retrieve the current
         // memento from the circle, and store it in the _previousMementos
         // array. Then set the state of the circle to random values.
         private function onClick(event:MouseEvent):void {
            _nextMementos = new Array();
            _previousMementos.push(_circle.getMemento());
            _circle.radius = Math.random() * 40 + 10;
            _circle.color = Math.random() * (255 * 255 * 255);
         }

         // When the user presses the right and left keys restore the
         // state of the circle by retrieving a memento from the appropriate
         // array and passing it to the setMemento() method of the circle.       
         private function onKey(event:KeyboardEvent):void {
            var memento:CircleMemento;
            if(event.keyCode == Keyboard.LEFT) {
            if(_previousMementos.length > 0) {
               memento = _previousMementos.pop();
            _nextMementos.push(memento);
               _circle.setMemento(memento);
            }
          }
          else if(event.keyCode == Keyboard.RIGHT) {
            if(_nextMementos.length > 0) {
               memento = _nextMementos.pop();
            _previousMementos.push(memento);
               _circle.setMemento(memento);
            }
          }
       }
       }
    }

This should give you a basic idea of the structure of a relatively simple Memento pattern implementation. Throughout the chapter, we’ll look at additional examples.

Using Mementos to Make Actions Undoable in the Proximity Game

Often, mementos are used in conjunction with commands in order to implement complex undoable and redoable commands. The following application applies mementos to the Proximity game application you created in the previous chapter, “Command Pattern”; the mementos will make the commands undoable in the Proximity game.

Defining the Memento Type

The first thing we’ll do is define a memento class. The class com.peachpit.aas3wdp.proximity.mementos.GamePieceMemento serves as the memento type used to store game piece state.

     package com.peachpit.aas3wdp.proximity.mementos {

        import com.peachpit.aas3wdp.proximity.data.GamePlayer;

        public class GamePieceMemento {

           private var _count:uint;
           private var _owner:GamePlayer;

           public function get count():uint {
              return _count;
           }

           public function get owner():GamePlayer {
              return _owner;
           }

           public function GamePieceMemento(count:uint, owner:GamePlayer) {
              _count = count;
              _owner = owner;
           }

        }
     }

You can see that the memento in this case stores values for count and owner. These values represent state for a PieceData object.

Creating the Originator

In the Proximity game, the mementos we want to store are for PieceData objects. Therefore, we’ll need to make the PieceData class an originator by adding getMemento() and setMemento() methods. Here’s PieceData with the new methods (we’ve omitted some of the code here just for the purposes of saving printed space):

     package com.peachpit.aas3wdp.proximity.data {

        import flash.events.EventDispatcher;
        import flash.events.Event;
        import com.peachpit.aas3wdp.proximity.data.GamePlayer;
        import com.peachpit.aas3wdp.proximity.data.NullOwner;
        import com.peachpit.aas3wdp.proximity.mementos.GamePieceMemento;

        public class PieceData extends EventDispatcher {

           // Existing code goes here.

           public function getMemento():GamePieceMemento {
             return new GamePieceMemento(_count, _owner);
           }
           public function setMemento(memento:GamePieceMemento):void {
              _count = memento.count;
              _owner = memento.owner;
              dispatchEvent(new Event(Event.CHANGE));
           }
     
        }
    }

You can see that the getMemento() method simply constructs and returns a new GamePieceMemento object. The setMemento() method takes a GamePieceMemento instance, restores the PieceData state, and dispatches an event to notify listeners that the data model has changed.

Defining the Undoable Command Type

Next we’ll define an undoable command type. The undoable command should inherit from the standard command type (GamePlayCommand). In addition, the command needs to implement the IUndoableCommand interface. The class, com.peachpit.aas3wdp.proximity.commands.UndoableGamePlayCommand is as follows:

     package com.peachpit.aas3wdp.proximity.commands {

        import com.peachpit.aas3wdp.proximity.data.PieceData;
        import com.peachpit.aas3wdp.proximity.data.GamePlayer;
        import com.peachpit.aas3wdp.proximity.data.GamePlayers;
        import com.peachpit.aas3wdp.proximity.data.GameboardData;
        import com.peachpit.aas3wdp.proximity.commands.GamePlayCommand;
        import com.peachpit.aas3wdp.iterators.IIterator;
        import com.peachpit.aas3wdp.proximity.mementos.GamePieceMemento;
        import com.peachpit.aas3wdp.proximity.data.GameboardData;
        import com.peachpit.aas3wdp.proximity.data.NullOwner;
        import com.peachpit.aas3wdp.proximity.data.PieceData;
        import com.peachpit.aas3wdp.commands.CommandStack;
        import com.peachpit.aas3wdp.commands.IUndoableCommand;

        public class UndoableGamePlayCommand extends GamePlayCommand implements
           IUndoableCommand {

           protected var _gamePieceMementos:Array;
           protected var _gameboardMemento:GamePieceMemento;


           public function UndoableGamePlayCommand(piece:PieceData) {
              super(piece);
              _gamePieceMementos = new Array();
           }

           override public function execute():void {
              var gameboard:GameboardData = GameboardData.getInstance();
              if(_piece.owner is NullOwner) {

                 // Get the memento for the clicked game piece.
                 _gamePieceMementos.push({object:
                 _piece, memento: _piece.getMemento()});
                 var iterator:IIterator = gameboard.getProximityPieces(_piece);
                 var piece:PieceData;
                 while(iterator.hasNext()) {
                    piece = PieceData(iterator.next());

                    // Add a memento for the adjacent 
                    // game piece.
                    _gamePieceMementos.push({object: piece, memento: piece.getMemento()});
                 }

                 // Add a memento for the new game piece and for the gameboard.
                 _gameboardMemento = gameboard.getMemento();
              }
              super.execute();
              CommandStack.getInstance().putCommand(this);
           }

           public function undo():void {
             for(var i:uint = 0; i < _gamePieceMementos.length; i++) {
                _gamePieceMementos[i].object.setMemento(_gamePieceMementos[i].memento);
             }
             GameboardData.getInstance().setMemento(_gameboardMemento);
          }

        }
     }

This class overrides the execute() method so that it can retrieve all the mementos for the affected game pieces before changing their state. Then the undo() method loops through all the mementos and restores the originator state for each affected game piece.

Updating the Command Factory

Next we want to edit CommandFactory so that it returns a UndoableGamePlayCommand instance when the option is set correctly. Here’s the updated getGamePlayCommand() method:

       public static function getGamePlayCommand(data:PieceData):ICommand {
          if(_type == NORMAL) {
             return new GamePlayCommand(data);
          }
          else if(_type == UNDOABLE) {
            return new UndoableGamePlayCommand(data);
          }
          return null;
      }

Updating the Main Class

Next we can edit the main class to enable undoable commands in the game. The behavior we are striving for is to undo commands when the user presses the left-arrow key.

The first thing we need to do in the main class is edit the constructor and assign UNDOABLE rather than NORMAL to the CommandFactory.type property:

     CommandFactory.type = CommandFactory.UNDOABLE

Next we’ll add keyboard control. To do this the class must import the Keyboard and KeyboardEvent classes:

     import flash.events.KeyboardEvent;
     import flash.ui.Keyboard;

Then we’ll add the following line of code to the main class constructor to listen for keyboard events:

     stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyboard);

Add an onKeyboard() method to the main class. Define the method as follows:

     private function onKeyboard(event:KeyboardEvent):void {
        var stack:CommandStack = CommandStack.getInstance();
        var command:ICommand;

        // If the user pressed the left arrow and there are
        // previous commands in the stack,
        // and if the command is undoable, call undo().
        if(event.keyCode == Keyboard.LEFT && stack.hasPreviousCommands()) {
           command = stack.previous();
           if(command is IUndoableCommand) {
              IUndoableCommand(command).undo();
           }
           else {
             stack.next();
           }
        }
     }

Those few changes make the game’s play actions undoable. When you test the Proximity application now, you can use the left-arrow key to undo the actions you have applied.

Using Mementos to Make Actions Redoable in the Proximity Game

Now that we have added an undo feature to the Proximity game, we’ll complete our modifications to the game by adding code that redoes actions we have just undone.

Defining the Redoable Command

The first step in making the commands redoable is to create a redoable command class. Define a class, com.peachpit.aas3wdp.proximity.commands.RedoableGamePlayCommand, that extends UndoableGamePlayCommand and adds redo functionality by implementing IRedoableCommand.

     package com.peachpit.aas3wdp.proximity.commands {

        import com.peachpit.aas3wdp.proximity.data.PieceData;
        import com.peachpit.aas3wdp.proximity.data.GamePlayer;
        import com.peachpit.aas3wdp.proximity.data.GamePlayers;
        import com.peachpit.aas3wdp.proximity.data.GameboardData;
        import com.peachpit.aas3wdp.proximity.commands.UndoableGamePlayCommand;
        import com.peachpit.aas3wdp.iterators.IIterator;
        import com.peachpit.aas3wdp.proximity.mementos.GamePieceMemento;
        import com.peachpit.aas3wdp.commands.IRedoableCommand;
        import com.peachpit.aas3wdp.proximity.data.NullOwner;

        public class RedoableGamePlayCommand extends UndoableGamePlayCommand implements 
           IRedoableCommand {

           private var _nextGamePieceMemento:GamePieceMemento;

           public function RedoableGamePlayCommand(piece:PieceData) {
              super(piece);
           }

           override public function undo():void {
              _nextGamePieceMemento = GameboardData.getInstance().newGamePiece.getMemento();
              super.undo();
           }

           public function redo():void {
              var gameboard:GameboardData = GameboardData.getInstance();
              var newGamePiece:PieceData = gameboard.newGamePiece;
              var currentGamePlayer:GamePlayer = newGamePiece.owner;
              _piece.owner = currentGamePlayer;
              _piece.count = newGamePiece.count;

              // Retrieve all adjacent pieces.
              var iterator:IIterator = gameboard.getProximityPieces(_piece);
              var piece:PieceData;
              while(iterator.hasNext()) {
                 piece = PieceData(iterator.next());
                 // If the game piece has the same owner as 
                 // the clicked game piece, increment the
                 // count. If they have different owners (and 
                 // the owner isn't NullOwner) then test if 
                 // the clicked game piece has a higher
                 // count. If so, make it the new owner.
                 if(piece.owner == _piece.owner) {
                    piece.count++;
                 }
                 else if(!(piece.owner is NullOwner)) {
                   if(piece.count < _piece.count) {
                      piece.owner = currentGamePlayer;
                   }
                 }
              }
              GameboardData.getInstance().setMemento(_nextGamePieceMemento);
            }

         }
      }

The redoable command redoes a command by essentially replaying based on the new game piece. It then uses a memento to restore the next new game piece state.

Editing the Factory Class

Next we’ll edit the CommandFactory class so that it returns a RedoableGamePlayCommand object when the type property is set to REDOABLE. Here’s the updated getGamePlayCommand() method:

     public static function getGamePlayCommand(data:PieceData):ICommand {
        if(_type == NORMAL) {
           return new GamePlayCommand(data);
        }
        else if(_type == UNDOABLE) {
          return new UndoableGamePlayCommand(data);
        }
        else if(_type == REDOABLE) {
           return new RedoableGamePlayCommand(data);
        }
        return null;
    }

Editing the Main Class

Now we can edit the main class by assigning a value of REDOABLE rather than UNDOABLE to the CommandFactory.type property.

     CommandFactory.type = CommandFactory.REDOABLE;

Add an if clause to the onKeyboard() method so that it calls the redo() method of the next command object in the stack when the user presses the right-arrow key:

     private function onKeyboard(event:KeyboardEvent):void {
        var stack:CommandStack = CommandStack.getInstance();
        var command:ICommand;

        if(event.keyCode == Keyboard.LEFT && stack.hasPreviousCommands()) {
           command = stack.previous();
           if(command is IUndoableCommand) {
              IUndoableCommand(command).undo();
              }
              else               {
                stack.next();
              }
           }

           // If the user pressed the right arrow key and there are next
           // commands in the stack, and if the command is redoable, call 
           // redo().
          if(event.keyCode == Keyboard.RIGHT && stack.hasNextCommands()) {
             command = stack.next();
             if(command is IRedoableCommand) {
                IRedoableCommand(command).redo();
             }
             else {
                stack.previous();
             }
         }
     }

When you test the application now, you can press the right-arrow key to redo any action that you’ve previously undone.

Summary

This chapter discusses the Memento pattern, which provides an elegant way to store an object’s state while at the same time breaking no rules of encapsulation. An object for which you need to record state is called the originator. The originator is responsible for returning a memento object that stores the object’s current state, and the originator is also responsible to managing its own state by applying a stored state from a memento. The memento objects can be stored by a caretaker object until they are reapplied to the originator.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.22.162