Tuesday, November 3, 2009

Snapshot




















Chapter 8 -
Behavioral Patterns
Patterns in Java, Volume 1: A Catalog of Reusable Design Patterns Illustrated with UML, Second Edition
by Mark Grand
John Wiley & Sons � 2002



























Snapshot



This pattern is based partially on the Memento pattern documented in [GoF95].




Synopsis


Capture a snapshot of an object’s state so that the object’s state can be restored later. The object that initiates the capture or restoration of the state does not need to know anything about the state information. It only needs to know that the object whose state it is restoring or capturing implements a particular interface.






Context


Suppose that you are writing a program to a play a role-playing game. For the purposes of this discussion, the details of the games are not important. What is important is that it is a single-player game. To play the game, a player directs a character to interact with various computer-controlled characters and simulated objects. One way that a game can end is for the character under the player’s control to die. Players of the game will not consider this a desirable outcome.


Among the many features planned for the game are two features that involve saving and restoring the state of the game. The program needs these features because playing one of these games to its conclusion can take a few days of nonstop play.














l



To allow a player to play the game over multiple short intervals, it must be possible to save the state of the game to a file so that it can be continued later.




l



To arrive at the game’s conclusion, a player must successfully guide his or her character through many adventures. If the player’s character dies before the game is over, the player will have the option of starting the game over from the very beginning. That may be an unattractive option because the player may be well into the game and have played through the earlier portions of the game a number of times. The program will also offer the player the option of resuming the game at an earlier point than when the character died.


It will do this by saving part of the game’s state, including credit for the character’s previous experiences and a record of some of the character’s possessions. It will perform a partial state save when the player’s character has accomplished a major task. As the game proceeds, these checkpoints become part of the game’s over all state, needing to be saved when the rest of the state is saved to disk.





Though the game will involve many classes, there are only a few that will share the responsibility for creating these snapshots of the game’s state:


The classes in Figure 8.14 participate in two distinct state-saving mechanisms:






Figure 8.14: Game snapshot classes.













l



There is a mechanism for saving part of a game’s state when the player’s character achieves a milestone.




l



There is a mechanism for saving and restoring an entire game.




There are two classes in Figure 8.14 that participate in both mechanisms:



UserInterface. All player-initiated actions come through the UserInterface class. The UserInterface class delivers most of the actions that a player initiates to an instance of the GameModel class. However, player-initiated snapshots of the game follow different routes, which are discussed subsequently.



GameModel. The GameModel class is responsible for maintaining the state of the game during play. The UserInterface class notifies an instance of the GameModel class when the player does something related to the game. The instance of the GameModel class determines what the consequences of that action are, modifies the state of the game accordingly, and notifies the user interface. An instance of the GameModel class may also initiate some actions. It will always report the consequences of any action it initiates to the user interface but will not always report the action itself.


The UserInterface class’s involvement in making snapshots is to initiate one kind of snapshot and both kinds of restores. Because the GameModel class is the top-level class responsible for the state of the game, it is involved in any operation that manipulates the game’s state.


These are the other classes and interfaces involved in performing partial state saves and restores:



MilestoneMemento. MilestoneMemento is a private class defined by the GameModel class. A GameModel object creates instances of MilestoneMemento that contain copies of the values that make up the partial state to be saved. Given a MilestoneMemento object, a GameModel object can restore itself to the previous state contained in the MilestoneMemento object.



MilestoneMementoIF. The MilestoneMemento class implements this interface. This interface is public. Outside of the GameModel class, instances of the MilestoneMemento class can be accessed only as instances of the Object class or through the MilestoneMementoIF interface. Neither mode of access allows an object to access the state information encapsulated in MilestoneMemento objects.



MilestoneMementoManager. The MilestoneMementoManager class contributes to the decision to create MilestoneMemento objects. It also manages their use after they are created.


Here is how the capture of a game’s partial state happens after the player’s character has achieved a milestone:




  1. A GameModel object enters a state indicating that the player’s character has achieved one of the game’s major milestones.




  2. There is a MilestoneMementoManager object associated with each GameModel object. When the GameModel object enters a milestone state, it calls the associated MilestoneMementoManager object’s snapshotMilestone method. It passes to the method a string that is a description of the milestone. The player’s character may previously have achieved this milestone, died, and then returned to an earlier milestone. If a MilestoneMemento object already exists for a milestone, then another MilestoneMemento object is not created for the milestone.




  3. A MilestoneMementoManager object determines whether a MilestoneMemento object already exists for a milestone by comparing the description string passed to its snapshotMilestone method to the descriptions of the MilestoneMemento objects that already exist. If a MilestoneMemento object already exists with the given description, then the snapshotMilestone method takes no additional action.




  4. If the MilestoneMementoManager object determines that no MilestoneMemento object already exists for the milestone, then the MilestoneMementoManager object initiates the creation of a MilestoneMemento object to capture the game’s partial state at that time. It does this by calling the GameModel object’s createMemento method, passing it the same description that was passed to the MilestoneMementoManager object.




  5. The createMemento method returns a freshly created MilestoneMemento object. The MilestoneMementoManager object adds the new MilestoneMemento object to its collection of MilestoneMementoIF objects.




When a player’s character dies, the UserInterface object offers the player the option for the character to start from a previously achieved milestone, rather than from the very beginning. It offers the player a list of milestones to choose from by calling the MilestoneMementoManager object’s getMilestoneMementos method. This method returns an array of Milestone Memento objects that the MilestoneMementoManager object has collected.


If the player indicates that he or she wants the character to start from one of its previously achieved milestones, the UserInterface object passes the corresponding MilestoneMemento object to the MilestoneMemento Manager object’s restoreFromMemento method. This method, in turn, calls the GameModel object’s setMemento method, passing it the chosen Milestone Memento object. Using the information in the MilestoneMemento object, the GameModel object restores its state.


The other snapshot mechanism saves the complete state of the game to a file, including the MilestoneMementoManager object and its collection of MilestoneMemento objects. This mechanism is based on Java’s serialization facility.


If you are unfamiliar with Java’s serialization facility, it is a way to copy the state of an object to a stream of bytes and then create a copy of the original object from the contents of the byte stream. There is a somewhat more detailed description of serialization under the Implementation heading of this pattern.


The classes that are involved in saving and restoring a complete snapshot of the game’s state to and from a file are:



Serializer. The Serializer class is responsible for serializing a GameModel object. It copies the state information of the GameModel object and all other objects it refers to that are part of the game’s state as a byte stream to a file.



FileOutputStream. This is the standard Java class java.io.FileOutputStream. It writes a stream of bytes to a file.



Deserializer. The Deserializer class is responsible for reading a serialized byte stream and creating a copy of the GameModel object and other objects that were serialized to create the byte stream.



FileInputStream. This is the standard Java class java.io.FileInputStream. It reads a stream of bytes from a file.


Here is the sequence of events that occurs when the user requests that the game be saved to a file or restored from a file:




  1. The player tells the user interface that he or she wants to save the game to a file. The UserInterface object then creates a Serializer object, passing the name of the file and a reference to the GameModel object to its constructor. The Serializer object creates an ObjectOutputStream object and a FileOutputStream object. It uses the ObjectOutputStream object to serialize the GameModel object and all other game-related objects it refers to into a byte stream. It uses the FileOutputStream object to write that byte stream to a file.




  2. When the player wants to restore the game from a file, he or she tells the user interface. The UserInterface object creates a Deserializer object, passing the name of the file and a reference to the GameModel object to its constructor. The Deserializer object creates an ObjectInputStream object and a FileInputStream object. It uses the FileInputStream object to read a serialized byte stream from a file. It uses the ObjectInputStream object to deserialize the GameModel object and all other game-related objects it refers to from the byte stream.




Most of the patterns in this book describe only one way to solve a problem. The Snapshot pattern is different. It describes two ways of solving the problem of making a snapshot of an object’s state.






Forces














J



You need to create a snapshot of an object’s state and also be able to restore the state of the object.




J



You want a mechanism that saves and restores an object’s state to be independent of the object’s internal structure, so that the internal structure can change without having to modify the save/restore mechanism.








Solution


The following are two general solutions to the problem of saving a snapshot of an object’s state and restoring its state from the snapshot. First is a description of using Memento objects to create a nonpersistent copy of an object’s partial state. Then there is a description of how to use serialization to save and restore an object’s state. That is followed by a comparison of the two techniques.



Figure 8.15 shows the general organization for objects that use Memento objects to save and restore an object’s state.






Figure 8.15: Snapshot using memento objects.


Here are descriptions of the roles the classes play in the variation of the Snapshot pattern that uses Memento objects:



Originator.  A class in this role is a class whose instance’s state information is to be saved and restored. When its createMemento method is called, it creates a Memento object that contains a copy of the Originator object’s state information. Later, you can restore the Originator object’s state by passing a Memento object to its setMemento method.



Memento.  A class in this role is a private static class of the Originator class that implements the MementoIF interface. Its purpose is to encapsulate snapshots of an Originator object’s state. Because it is a private member of the Originator class, only the Originator class is able to access it. Other classes must access instances of the Memento class either as instances of Object or through the MementoIF interface.



MementoIF.  Classes other than the Originator class access Memento objects through this interface. Interfaces in this role may declare no methods. If they do declare any methods, the methods should not allow the encapsulated state to be changed. This ensures the consistency and integrity of the state information. Interfaces in this role are designed using the Read-Only Interface pattern.



Caretaker.  Instances of classes in this role maintain a collection of Memento objects. After a Memento object is created, it is usually added to a Caretaker object’s collection. When an undo operation is to be performed, a Caretaker object typically collaborates with another object to select a Memento object. After the Memento object is selected, it is typically the Caretaker object that calls the Originator object’s setMemento method to restore its state.


The other mechanism for creating a snapshot of an object’s state is serialization. Serialization is different from most other object-oriented techniques in that it works by violating the encapsulation of the object being serialized. Most, but not necessarily all, of the violation is through Java’s reflection mechanism. This is explained more fully by Figure 8.16 and the following descriptions of roles that classes play in this form of the Snapshot pattern.






Figure 8.16: Snapshot using serialization.

Here are descriptions of the roles classes play in the variation of the Snapshot pattern that uses serialization:



Target.  An ObjectOutputStream object converts the state of instances of classes in this role to a byte stream. An ObjectInputStream object restores the state of instances of classes in this role from a byte stream. The role of the Target object in these activities is purely passive. The ObjectOutputStream object or ObjectInputStream object does all of the work.



ObjectOutputStream.  The class in this role is usually the standard Java class java.io.ObjectOutputStream. It discovers and accesses a Target object’s state information and writes it to a byte stream with additional information that allows an ObjectInputStream object to restore the state information.



OutputStream.  An object in this role is an instance of a subclass of the standard Java class java.io.OutputStream. If the state information needs to be saved indefinitely, then the OutputStream object may be a FileOutputStream. If the state information needs to be saved no longer than the duration of a program run, then the OutputStream object may be a ByteArrayOutputStream.



ObjectInputStream.  The class in this role is a subclass of the standard Java class java.io.ObjectInputStream. Instances of these classes read serialized state information from a byte stream and restore it.


If you do not override the default behavior, an Object InputStream object puts the original Target object’s state information in a new instance of the Target object’s class. Using techniques described under the Implementation heading, you can arrange for ObjectInputStream objects to restore the saved state to an existing instance of the Target class.



Table 8.3 shows some key differences between the two techniques for creating and managing snapshots of an object’s state.


























Table 8.3: Comparison of State Saving by Serialization and Memento Objects
 


Serialization




Memento




Persistence



You can use serialization to save state in a persistent form by serializing it to a file



Using Memento objects does not provide persistence.




Complexity of implementation



Serialization can be the simpler technique for saving the entire state of an object. This is especially true for objects whose state includes references to other objects whose state must be also be saved.



Using Memento objects is often a simpler way to capture part of an object’s state.




Object Identity



Absolute object identity is lost unless you supply additional code to preserve it. The default way that serialization restores an object’s state is by creating a copy of the object. If the original object contains other objects and there are multiple references to the same object, then the duplicate object will contain references to an identical but distinct object. Among the objects referred to by the restored object, serialization preserves relative object identity.



Object identity is preserved. Using Memento objects is a simpler way to restore the state of an object so that it refers to the same object that it referred to before.




Overhead



Using serialization adds considerable overhead to the process of creating a snapshot to be saved in memory. The bulk of the overhead comes from the fact that serialization works through Java’s reflection mechanism and creates new objects when restoring state.



There is no particular overhead associated with using Memento objects.




Expertise Required



In cases where you need to make a snapshot of an object’s complete state, all of the objects involved implement the Serializable inter-face, and preserving object identity is not important, serialization requires minimal expertise. As situations vary from these constraints, the required level of expertise quickly increases. Some situations may require an in-depth knowledge of serialization internals, reflection, and other arcane aspects of Java.



Using Memento objects requires no specialized knowledge.







Implementation



Using Memento objects to make snapshots of an object’s state is very straightforward to implement. Using serialization requires additional expertise and sometimes additional complexity. A complete description of serialization is beyond the scope of this book.1 What follows is a description of some features of serialization relevant to the Snapshot pattern.


To serialize an object you first create an ObjectOutputStream object. You can do this by passing an OutputStream object to its constructor, like this:




FileOutputStream fout = new FileOutputStream("filename.ser");
ObjectOutputStream obOut = new ObjectOutputStream(fout);


The ObjectOutputStream object will write the byte stream it produces to the OutputStream object passed to its constructor.


Once you have created an ObjectOutputStream object, you can serialize an object by passing it to the ObjectOutputStream object’s writeObject method, like this:



ObOut.writeObject(foo);

The writeObject method uses Java’s reflection facility to discover the instance variables of the object that foo references and access them. If the value of an instance variable is a primitive type such as int or double, its value is written directly to the byte stream. If the value of an instance variable is a reference to another object, then the writeObject method also serializes that object.


Turning a serialized byte stream into an object is called deserialization. To deserialize a byte stream, you first create an ObjectInputStream object. You can do that by passing an InputStream object to its constructor, like this:



FileInputStream fin = new FileInputStream("filename.ser");
ObjectInputStream obIn = new ObjectInputStream(fin);

The ObjectInputStream object will read bytes from the input stream passed to its constructor.


Once you have created an ObjectInputStream object, you can deserialize its associated byte stream by calling its readObject method, like this:



GameModel g = (GameModel)obIn.readObject();

The readObject method is declared to return a reference to an Object. Since you will usually want to treat the object it returns as an instance of a more specialized class, you will usually typecast the result of the readObject method to a more specialized class.


There is one other thing that you must do in order to serialize an object. You can serialize an instance of a class only if the class gives its permission to be serialized. A class permits the serialization of its instances if the class implements the interface java.io.Serializable, like this:



import java.io.serializable;
...
class foo extends bar implements Serializable {

The Serializable interface is a marker interface. It does not declare any members. Declaring that a class implements the Serializable interface is simply a way to indicate that it may be serialized. If you pass an object to an ObjectOutputStream object’s writeObject method that does not implement the Serializable interface, then the method throws an exception.


So far, serialization seems simple. Though there are many situations in which the preceding details are all that you need to know, there are also many situations that are more complex.


The default behavior, when serializing an object, is to also serialize all of the objects that it refers to and all of the objects that they refer to, until the complete set has been serialized. Though an object may be an instance of a class that implements the Serializable interface, if it refers to any objects that are not Serializable, then any attempt to serialize the object will fail. It will fail when the ObjectOutputStream object’s writeObject method calls itself recursively to serialize the object that cannot be serialized and throws an exception. There is a way to avoid this problem.


You can specify that the serialization mechanism should ignore some of an object’s instance variables. The simplest way to do this is to declare the variable with the transient modifier, like this:



transient ObjectOutputStream obOut;

Because the serialization mechanism ignores transient variables, it does not matter to the serialization mechanism if a transient variable refers to an object that cannot be serialized.


There is another problem that can be solved by declaring some instance variables transient. Instances of some classes refer to other objects that refer to many objects that do not need to be saved. Serializing those objects would just add overhead to serialization and deserialization. If the value of an instance variable does not need to be part of an object’s serialized state, then declaring the instance variable transient avoids the overhead of unnecessarily serializing and deserializing its values.


Declaring instance variables transient solves a few problems during serialization. It also creates a problem during deserialization. The serialized byte stream does not contain values for transient variables. If you make no other arrangements, after deserialization an object’s transient variables will contain the default value for their declared type. For example, transient variables declared with a numeric type will have the value 0. Transient variables that are declared as an object type will have the value null. Serialization ignores initializers and constructors. Unless it is acceptable for an object to suddenly find its transient variables unexpectedly set to null or zero, this is a problem.


The ObjectInputStream class provides mechanisms that allow you to modify the default way that deserialization handles transient variables. What it allows you to do is to provide code that is executed after deserialization has performed its default actions. Often, that code requires information to reconstruct the values of transient variables that are not provided by the default actions of serialization. The ObjectOutputStream class provides mechanisms that allow you to add additional information to the information provided by the default actions of serialization. If you can add enough information to a serialized byte steam to be able to reconstruct the values of an object’s transient variables, then you have solved the problem.


To add information to what the ObjectOutputStream class’s writeObject method normally provides, you can add a method called writeObject to a serializable class. If an object is an instance of a class that defines a writeObject method in the required way, then instead of deciding how to handle the object’s instance variables internally, an ObjectOutputStream object calls that object’s writeObject method. This allows a class to determine how its own instance variables will be serialized.


To take responsibility for the serialization of its instance’s instance variables, a class should have a method like this:



private void writeObject(ObjectOutputStream stream)
                                        throws IOException {
    stream.defaultWriteObject();
...
}  // writeObject(ObjectOutputStream)

Notice that the method is private. It must be private to be recognized by an ObjectOutputStream object. These private writeObject methods are responsible for writing only those instance variables that their own classes declare. They are not responsible for variables declared by superclasses.


The first thing that most private writeObject methods do is to call the ObjectOutputStream object’s defaultWriteObject method. Calling this method causes the ObjectOutputStream object to perform default serialization actions for the class that called it. Next, a private writeObject method calls other methods of the ObjectOutputStream object to write any additional information that will be needed to reconstruct the values of transient variables. The ObjectOutputStream class is a subclass of DataOutputStream. It inherits methods to write strings and all of the primitive data types.


To make use of the additional information during deserialization, a class must also define a readObject method, like this:



private void readObject(ObjectInputStream stream)
                                        throws IOException {
    try {
        stream.defaultReadObject();
    } catch (ClassNotFoundException e) {
...
    } // try
...
} // readObject(ObjectInputStream)

Just as the writeObject method must be private, the readObject method must also be private for it to be recognized. It begins by calling the ObjectInputStream object’s defaultReadObject method. Calling this method causes the ObjectInputStream object to perform default deserialization actions for the class that called it. After this, a private readObject method will call other methods of the ObjectInputStream object to read any additional information that was supplied to reconstruct the values of transient variables. The ObjectInputStream class is a subclass of DataInputStream. It inherits methods to read strings and all of the primitive data types.


Here is an example to show how these private methods can fit together:



public class TextFileReader implements Serializable {
    private transient RandomAccessFile file;
    private String browseFileName;
...
    private
    void writeObject(ObjectOutputStream stream)
                                  throws IOException{
        stream.defaultWriteObject();
        stream.writeLong(file.getFilePointer());
    }  // writeObject(ObjectOutputStream)

    private
    void readObject(ObjectInputStream stream)
                                    throws IOException {
        try {
            stream.defaultReadObject();
        } catch (ClassNotFoundException e) {
            String msg = "Unable to find class";
            if (e.getMessage() != null)
              msg += ": " + e.getMessage();
            throw new IOException(msg);
        } // try
        file = new RandomAccessFile(browseFileName,
                                    "r");
        file.seek(stream.readLong());
    }  // readObject(ObjectInputStream)
}  // class TextFileReader

This class is called TextFileReader. It has an instance variable named file that refers to a RandomAccessFile object. The RandomAccessFile class does not implement the Serializable interface. For instances of TextFileReader to be successfully serialized and deserialized, it is not sufficient that the TextFileReader class implements the Serializable interface. It must also do the following:

















l



Prevent its reference to a RandomAccessFile object from being serialized.




l



Add additional information to the serialized byte stream so that it is possible to reconstruct the RandomAccessFile object.




l



Provide logic to allow the RandomAccessFile object to be reconstructed during deserialization.





To prevent its reference to a RandomAccessFile object from being serialized, the TextFileReader class declares its file variable to be transient.


The TextFileReader class has an instance variable that refers to a string that is the name of the file that the RandomAccessFile object accesses. This is sufficient information to create another RandomAccessFile object that accesses that file. However to make the state of the new RandomAccessFile object match the state of the original, it is necessary to add to the byte stream the RandomAccessFile object’s current position in the file. The TextFileReader class’s private writeObject method accomplished that.


To reconstruct the original object’s RandomAccessFile object, the TextFileReader class defines a private readObject method. This method reads the file position that was written to the serialized byte stream and passes it to the new RandomAccessFile object’s seek method.


Another issue you may need to deal with is the fact that the ObjectInputStream class’s readObject method normally returns a newly created object. In situations like the role-playing game described under the Context heading, this can be inconvenient. The inconvenience is that other objects already refer to the existing object. To modify their references to refer to the new object would be to involve those objects in the details of another object’s deserialization. The ObjectInputStream class allows you to resolve the situation without involving any class other than the one it is deserializing.


If the class being deserialized has a method named readResolve, then it can control which of its instances is returned by an ObjectInputStream object’s readObject method. The signature of the readObject method must look like this:


Object readResolve() throws ObjectStreamException ;

It does not matter whether the method is public, protected, or private. After an ObjectInputStream object’s readObject method has created an object that it is otherwise ready to return, it checks to see whether the object has a readResolve method. If the object does have a readResolve method, then the readObject method calls it and returns whatever objects the readResolve method returns. If the object that the readResolve method returns is not the newly created object, then it is up to the readResolve method to arrange for the object that it does return to contain the proper state information. You can see this technique used in the example that follows.






Consequences














J



Both forms of the Snapshot pattern keep a lot of the complexity of saving and restoring an object’s state out of its class.




L



The Snapshot pattern is not very suitable for undoing a fine-grained sequence of commands. Making many snapshots of an object can consume a prohibitive amount of storage. Capturing the changes to an object’s state (the Command pattern) may be more efficient.








Code Example


The following is some of the code to implement the design discussed under the Context heading. First is the code for the GameModel class, which is very central to any state-saving operation:



public class GameModel implements Serializable {
    private static GameModel theInstance = new GameModel();
    private MilestoneMementoManager mementoManager;
...
/**
* This constructor is private. Other classes must call
* this class's getGameModel method to get an instance.
*/
    private GameModel() {
        mementoManager = new MilestoneMementoManager(this);
...
    } // constructor()

/**
* Return the single instance of this class.
*/
    public static GameModel getGameModel() {
        return theInstance;
    }

The point of the preceding portion of the GameModel class is to make the GameModel class a singleton class. By making its constructor private, other classes are unable to use its constructor. This forces them to get an instance of the GameModel class by calling its getGameModel method.


When an ObjectInputStream object deserializes a byte stream, it uses a special mechanism that creates objects without calling constructors or evaluating variables’ initializers. After it has an instance of a class, it sets the object’s instance variables to the values it finds in the serialized byte stream.


After an ObjectInputStream object finishes deserializing an object, it checks to see whether the object has a readResolve method. If it does have a readResolve method, then it calls the object’s readResolve method and returns the object that the readResolve method returns rather than the object it just created.


Here is the GameModel class’s readResolve method, which sets the instance variables in the program’s GameModel object to the values in the deserialized GameModel object. It then returns the program’s GameModel object.



public Object readResolve() {
        GameModel theModel = getGameModel();
theModel.mementoManager = mementoManager;
...
        return theModel;
    }  // readResolve()

The remaining portions of the GameModel class related to the Snapshot pattern are involved in the management of memento objects.


There are a few noteworthy things about the implementation of the MilestoneMemento class. It is a private class of the GameModel class. This prevents any class other than the GameModel class from directly accessing its members. The MilestoneMemento class is declared static. This is a minor optimization that saves the expense of having MilestoneMemento objects maintain a reference to their enclosing GameModel object.


One other noteworthy aspect of the implementation of the MilestoneMemento class is its lack of access methods. Normally, it is good practice to require other classes to access an object’s instance variables only through accessor (get and set) methods. This practice results in well-encapsulated objects. Because of the close relationship between the MilestoneMemento class and the GameModel class, GameModel objects directly access the instance variables of MilestoneMemento objects. Because other classes can access the MilestoneMemento class only through the MilestoneMementoIF interface, a MilestoneMemento object’s instance variables are hidden from all other classes.




private static class MilestoneMemento
                               implements MilestoneMementoIF {
        private String description;
...
/**
* constructor
* @param description
* The reason for this object's creation.
*/
        MilestoneMemento(String description) {
            this.description = description;
} // constructor(String)

/**
* Return the reason why this memento was created.
*/
        public String getDescription() { return description; }

// These variables are set by a GameModel object
        MilestoneMementoManager mementoManager;
...
    }  // class MilestoneMemento


We will want to be able to serialize MilestoneMemento objects. You may notice that the MilestoneMemento class does not declare that it implements the Serializable interface. This is not necessary because it implements the MilestoneMementoIF interface and the MilestoneMementoIF interface extends the Serializable interface.


Following are methods that the GameModel class provides for creating memento objects and for restoring state from a memento object:


    /**
* Create a memento object that encapsulates a snapshot
* of this object's state.
*/
    MilestoneMementoIF createMemento(String description) {
// Create a memento object
// and set its instance variables.
        MilestoneMemento memento;
        memento = new MilestoneMemento(description);
        memento.mementoManager = mementoManager;
        ...
        return memento;
    } // createMemento(String)

/**
* Restore this object's state from the given memento
* object.
*/
    void setMemento(MilestoneMementoIF memento) {
        MilestoneMemento m = (MilestoneMemento)memento;
        mementoManager = m.mementoManager;
...
    } // setMemento(MilestoneMemento)





Related Patterns



Command. The Command pattern allows state changes to be undone on a command-by-command basis without having to make a snapshot of an object’s entire state after every command.



Read-Only Interface. Interfaces in the MementoIF role are designed using the Read-Only Interface pattern.


















No comments:

Post a Comment