Chapter 11. Memento Pattern


In This Chapter

Using Mementos to Make Actions Undoable in the Proximity game 173

Using Mementos to Make Actions Redoable in the Proximity game 177

Summary 180

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.




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