© Vaskaran Sarcar 2019
Vaskaran SarcarJava Design Patternshttps://doi.org/10.1007/978-1-4842-4078-6_19

19. Memento Pattern

Vaskaran Sarcar1 
(1)
Bangalore, Karnataka, India
 

This chapter covers the memento pattern.

GoF Definition

Without violating encapsulation, capture and externalize an object’s internal state so that the object can be restored to this state later.

Concept

In your application, you may need to support “undo” operations. To achieve this, you need to record the internal state of an object. So, you must save this state information in a place that can be referred again to revert back the old state of the object. But in general, objects encapsulate their states, and those states are inaccessible to the outer world. So, if you expose the state information, then you violate encapsulation.

The dictionary meaning of memento is reminder (of past events). So, you can guess that using this pattern, you can restore an object to its previous state, but it ensures that you achieve your goal without violating the encapsulation.

Real-World Example

A classic example in this category is noticed in a finite state machine. It is a mathematical model, but one of its simple applications can be found in a turnstile. It has rotating arms, which initially are locked. If you are allowed to pass through it (for example, when you insert coins or when a security person allows you to go through a security check), the locks are opened. Once you pass through, the turnstile returns to the locked state again.

Computer-World Example

In a drawing application, you may need to revert back to a prior state.

Note

You notice a similar pattern when you consider the JTextField class, which extends the javax.swing.text.JTextComponent abstract class and provides an undo support mechanism. Here javax.swing.undo.UndoManager can act as a caretaker, an implementation of javax.swing.undo. UndoableEdit can act like a memento, and an implementation of javax.swing.text.Document can act like an originator. You will learn about the terms originator, caretaker, and memento shortly. Also, java.io.Serializable is often called an example of a memento but although you can serialize a memento object, it is not a mandatory requirement for the memento design pattern.

Illustration

Go through the code and follow the comments for your ready reference. In this example, three objects are involved: a memento, an originator, and a caretaker. (These names are very common, so I have kept the same naming convention in our implementation.)

The originator object has an internal state. A client can set a state in it. A memento object may store as much or as little of the originator’s state, at the originator’s discretion. When a caretaker wants to record the state of the originator, it requests the current state from it. So, it first asks the originator for a memento object.

In the following example, the caretaker object confirms the save operation by displaying a console message. Suppose that the client makes some changes and then wants to revert back to the previous state. Since the originator object’s state is already changed, to roll back to the previous state requires help from the caretaker object, which saved the state earlier. The caretaker object returns the memento object (with the previous state) to the originator. The memento object itself is an opaque object (one which the caretaker is not allowed to make any change to, and ideally, only the originator, who created the memento can access the memento’s internal state).

So, you can conclude that caretaker has a narrow view/interface to the memento because it can only pass it to other objects. In contrast, the originator sees the wide interface because it can access the data necessary to return to a previous state.

Class Diagram

Figure 19-1 shows the class diagram.
../images/395506_2_En_19_Chapter/395506_2_En_19_Fig1_HTML.jpg
Figure 19-1

Class diagram

Package Explorer View

Figure 19-2 shows the high-level structure of the parts of the program.
../images/395506_2_En_19_Chapter/395506_2_En_19_Fig2_HTML.jpg
Figure 19-2

Package Explorer view

Implementation

Here is the implementation.
package jdp2e.memento.demo;
class Memento
{
    private int stateId;
    public Memento(int stateId)
    {
        this.stateId = stateId;
    }
    public int getStateId() {
        return stateId;
    }
    /*This class does not have the
    setter method.We need to use this class
    to get the state of the object only.*/
    /*public void setState(String state) {
        this.state = state;
    }*/
}
/*
The 'Originator' class
WikiPedia notes( for your reference):
Make an object (originator) itself responsible for:
1.Saving its internal state to a(memento) object and
2.Restoring to a previous state from a(memento) object.
3.Only the originator that created a memento is allowed to access it .
 */
class Originator
{
    private int stateId;
    public Originator()
    {
        this.stateId = 0;
        System.out.println(" Originator is created with state id : "+ stateId);
    }
    public int getStateId()
    {
        return stateId;
    }
    public void setStateId(int stateId)
    {
        System.out.println(" Setting the state id of the originator to : "+ stateId);
        this.stateId= stateId;
    }
    //Saving its internal state to a(memento) object
    public Memento saveMemento(int stateId)
    {
        System.out.println(" Saving originator's current state id. ");
        //Create memento with the current state and return it.
        return new Memento(stateId);
    }
    //Restoring to a previous state from a(memento) object .
    public void revertMemento(Memento previousMemento)
    {
        System.out.println(" Restoring to state id..."+ previousMemento.getStateId());
        this.stateId = previousMemento.getStateId();
        System.out.println(" Current state id of originator : "+ stateId);
    }
}
/*
The 'Caretaker' class.
WikiPedia notes( for your reference):
1.A client (caretaker) can request a memento from the originator  to save the internal state of the originator and
2.Pass a memento back to the originator (to restore to a previous state)
This enables to save and restore the internal state of an originator without violating its encapsulation.
 */
public class MementoPatternExample {
    public static void main(String[] args) {
        System.out.println("***Memento Pattern Demo*** ");
        //Originator is initialized with a state
        Originator originatorObject = new Originator();
        Memento mementoObject;
        originatorObject.setStateId(1);
        // A client (caretaker) can request a memento from the originator
        //to save the internal state of the originator
        mementoObject=originatorObject.saveMemento(originatorObject.getStateId());
        System.out.println(" Snapshot #1: Originator's current state id is saved in caretaker.");
        //A client (or caretaker) cannot set/modify the memento's state
        //mementoObject.setState("arbitratyState");//error
        //Changing the state id of Originator
        originatorObject.setStateId(2);
        mementoObject=originatorObject.saveMemento(originatorObject.getStateId());
        System.out.println(" Snapshot #2: Originator's current state id is saved in caretaker.");
        //Changing the state id of Originator again .
        originatorObject.setStateId(3);
        //Reverting back to previous state id.
        originatorObject.revertMemento(mementoObject);
    }
}

Output

Here is the output.
***Memento Pattern Demo***
 Originator is created with state id : 0
 Setting the state id of the originator to : 1
 Saving originator's current state id.
 Snapshot #1: Originator's current state id is saved in caretaker.
 Setting the state id of the originator to : 2
 Saving originator's current state id.
 Snapshot #2: Originator's current state id is saved in caretaker.
 Setting the state id of the originator to : 3
 Restoring to state id...2
 Current state id of originator : 2

Note

If you deal with a state that is a mutable reference type, you may need to do a deep copy to store the state inside the Memento object.

Q&A Session

  1. 1.

    I can restore the previous snapshot/restore point. But in a real-life scenario, I may have multiple restore points. How can you implement that using this design pattern?

    You can use an ArrayList in such a case. Consider the following program.

    The Originator class and Memento class are same as before, so I am presenting the modified Caretaker class only. I am using the following line of code in the upcoming implementation.

     
List<Memento> savedStateIds = new ArrayList<Memento>();
So, you need to include these two lines of code at the beginning.
import java.util.ArrayList;
import java.util.List;

Modified Caretaker Class

This is the modified Caretaker class.
   /*
The modified 'Caretaker' class.
WikiPedia notes( for your reference):
1.A client (caretaker) can request a memento from the originator to save the internal state of the originator and
2.Pass a memento back to the originator (to restore to a previous state)
This enables to save and restore the internal state of an originator
without violating its encapsulation.
 */
public class ModifiedMementoPatternExample {
    public static void main(String[] args) {
        System.out.println("***Modified Memento Pattern Demo*** ");
        List<Memento> savedStateIds = new ArrayList<Memento>();
        //Originator is initialized with a state
        Originator originatorObject = new Originator();
        Memento mementoObject;
        originatorObject.setStateId(1);
        mementoObject=originatorObject.saveMemento(originatorObject.getStateId());
        savedStateIds.add( mementoObject);
        System.out.println(" Snapshot #1: Originator's current state id is saved in caretaker.");
        //A client or caretaker cannot set/modify the memento's state
        //mementoObject.setState("arbitratyState");//error
        //Changing the state id of Originator
        originatorObject.setStateId(2);
        mementoObject=originatorObject.saveMemento(originatorObject.getStateId());
        savedStateIds.add( mementoObject);
        System.out.println(" Snapshot #2: Originator's current state id is saved in caretaker.");
        //Changing the state id of Originator
        originatorObject.setStateId(3);
        mementoObject=originatorObject.saveMemento(originatorObject.getStateId());
        savedStateIds.add( mementoObject);
        System.out.println(" Snapshot #3: Originator's current state id is saved in caretaker (client).");
        //Reverting back to previous state id.
        //originatorObject.revertMemento(mementoObject);
        //Reverting back to specific id -say, Snapshot #1)
        //originatorObject.revertMemento(savedStateIds.get(0));
        //Roll back everything...
        System.out.println("Started restoring process...");
        for (int i = savedStateIds.size(); i > 0; i--)
        {
            originatorObject.revertMemento(savedStateIds.get(i-1));
        }
    }
}

Modified Output

Once you run this modified program, you get the following output.
***Modified Memento Pattern Demo***
 Originator is created with state id : 0
 Setting the state id of the originator to : 1
 Saving originator's current state id.
 Snapshot #1: Originator's current state id is saved in caretaker.
 Setting the state id of the originator to : 2
 Saving originator's current state id.
 Snapshot #2: Originator's current state id is saved in caretaker.
 Setting the state id of the originator to : 3
 Saving originator's current state id.
 Snapshot #3: Originator's current state id is saved in caretaker (client).
Started restoring process...
 Restoring to state id...3
 Current state id of originator : 3
 Restoring to state id...2
 Current state id of originator : 2
 Restoring to state id...1
 Current state id of originator : 1

Analysis

In this modified program, you can see three different variations of “undo” operations.
  • You can just go back to the previous restore point.

  • You can go back to your specified restore point.

  • You can revert back to all restore points.

To see cases 1 and 2, uncomment the lines in the previous implementation.
  1. 2.

    In many applications, I notice that the memento class is presented as an inner class of Originator. Why are you not following that approach?

    The memento design pattern can be implemented in many different ways (for example, using package-private visibility or using object serialization techniques). But in each case, if you analyze the key aim, you find that once the memento instance is created by an originator, no one else besides its creator is allowed to access the internal state (this includes the caretaker/client). A caretaker’s job is to store the memento instance (restore points in our example) and supply them back when you are in need. So, there is no harm if your memento class is public. You can just block the public setter method for the memento. I believe that it is sufficient enough.

     
  2. 3.

    But you are still using the getter method getStateId(). Does it not violate the encapsulation?

    There is a lot of discussion and debate around this area—whether you should use getter/setter or not, particularly when you consider encapsulation. I believe that it depends on the amount of strictness that you want to impose. For example, if you just provide getter/setters for all fields without any reason, that is surely a bad design. The same thing applies when you use all the public fields inside the objects. But sometimes the accessor methods are required and useful. In this book, my aim is to encourage you learn design patterns with simple examples. If I need to consider each and every minute detail such as this, you may lose interest. So, in these examples, I show a simple way to promote encapsulation using the memento pattern. But, if you want to be stricter, you may prefer to implement the memento class as an inner class of the originator and modify the initial implementation, like in the following.
    package jdp2e.memento.questions_answers;
    /*
    The 'Originator' class
    WikiPedia notes( for your reference):
    Make an object (originator) itself responsible for:
    1.Saving its internal state to a(memento) object and
    2.Restoring to a previous state from a(memento) object.
    3.Only the originator that created a memento is allowed to access it.
     */
    class Originator
    {
        private int stateId;
        Memento myMemento;
        public Originator()
        {
            this.stateId = 0;
            System.out.println(" Originator is created with state id : "+ stateId);
        }
        public int getStateId()
        {
            return stateId;
        }
        public void setStateId(int stateId)
        {
            System.out.println(" Setting the state id of the originator to : "+ stateId);
            this.stateId= stateId;
        }
        //Saving its internal state to a(memento) object
        public Memento saveMemento()
        {
            System.out.println(" Saving originator's current state id. ");
            //Create memento with the current state and return it.
            return new Memento(this.stateId);
        }
        //Restoring to a previous state from a(memento) object.
        public void revertMemento(Memento previousMemento)
        {
            System.out.println(" Restoring to state id..."+ previousMemento.getStateId());
            this.stateId = previousMemento.getStateId();
            System.out.println(" Current state id of originator : "+ stateId);
        }
        //A memento class implemented as an inner class of Originator
        static class Memento
        {
            private int stateId;
            public Memento(int stateId)
            {
                this.stateId = stateId;
            }
            //Only outer class can access now
            public int getStateId() {
                return stateId;
            }
            /*This class does not have the
            setter method.We need to use this class
            to get the state of the object only.*/
            /*public void setState(String state) {
                this.state = state;
            }*/
        }
    }
    /*
    The 'Caretaker' class.
    WikiPedia notes( for your reference):
    1.A client (caretaker) can request a memento from the originator
    to save the internal state of the originator and
    2.Pass a memento back to the originator (to restore to a previous state)
    This enables to save and restore the internal state of an originator without violating its encapsulation.
     */
    public class MementoAsInnerClassExample {
        public static void main(String[] args) {
            System.out.println("***Memento Pattern Demo*** ");
            //Originator is initialized with a state
            Originator originatorObject = new Originator();
            Originator.Memento mementoObject;
            originatorObject.setStateId(1);
            // A client (caretaker) can request a memento from the originator
            //to save the internal state of the originator
            mementoObject=originatorObject.saveMemento();
            System.out.println(" Snapshot #1: Originator's current state id is saved in caretaker.");
            //A client (or caretaker) cannot set/modify the memento's state
            //Changing the state id of Originator
            originatorObject.setStateId(2);
            mementoObject=originatorObject.saveMemento();
            System.out.println(" Snapshot #2: Originator's current state id is saved in caretaker.");
            //Changing the state id of Originator again.
            originatorObject.setStateId(3);
            //Reverting back to previous state id.
            originatorObject.revertMemento(mementoObject);
        }
    }
     
  3. 4.
    What are the key advantages of using a memento design pattern?
    • The biggest advantage is that you can always discard the unwanted changes and restore it to an intended or stable state.

    • You do not compromise the encapsulation associated with the key objects that are participating in this model.

    • Maintains high cohesion.

    • Provides an easy recovery technique.

     
  4. 5.
    What are key challenges associated with a memento design pattern?
    • A high number of mementos require more storage. At the same time, they put additional burdens on a caretaker.

    • The preceding point increases maintenance costs in parallel.

    • You cannot ignore the time to save these states. The additional time to save the states decreases the overall performance of the system.

     
Note In a language like C# or Java, developers may prefer the serialization/deserialization techniques instead of directly implementing a memento design pattern. Both techniques have their own pros/cons. But you can also combine both techniques in your application.
  1. 6.

    In these implementations, if you make the originator’s state public, then our clients also could directly access the states. Is this correct?

    Yes. But you should not try to break the encapsulation. Notice the GoF definition that begins “without violating encapsulation…”

     
  2. 7.

    In these implementations, the memento class does not have a public setter method. What is the reason behind this?

    Go through the answer of question 2 again. And read the comment in the code that says, “Only the originator that created a memento is allowed to access it.” So, if you do not provide a public setter method for your memento class, the caretaker or client cannot modify the memento instances that are created by an originator.

     
  3. 8.
    In these implementations, you could ignore the getter method of the memento by using package-private visibility for stateId. For example, you could code memento class like the following.
    class Memento
    {
        //private int stateId;
        int stateId;//-Change is here
        public Memento(int stateId)
        {
            this.stateId = stateId;
        }
        public int getStateId() {
            return stateId;
        }
        /*This class does not have the
        setter method.We need to use this class
        to get the state of the object only.*/
        /*public void setState(String state) {
            this.state = state;
        }*/
    }
    And then you can use the following line.
            //System.out.println(" Restoring to state id..."+ previousMemento.getStateId());
            System.out.println(" Restoring to state id..."+ previousMemento.stateId);//←The change is shown in bold

    Is this correct?

    Yes. In many application, other classes (except originator) cannot even read the memento’s state. When you use package-private visibility, you do not need any accessor method. In other words, you are simply using the default modifier in this case.

    So, this kind of visibility is slightly more open than private visibility and other classes in the same package can access a class member. So, in this case, the intended classes need to be placed inside the same package. At the same time, you need to accept that all other classes inside the same package will have the direct access to this state. So, you need to be careful enough when you place the classes in your special package.

     
  4. 9.

    I am confused. To support undo operations, which pattern should I prefer—memento or command?

    The GoF told us that these are related patterns. It primarily depends on how you want to handle the situation. For example, suppose that you are adding 10 to an integer. After this addition, you want to undo the operation by doing the reverse operation (i.e., 50 + 10 = 60, so to go back, you do 60 –10 = 50). In this type of operation, we do not need to store the previous state.

    But consider a situation where you need to store the state of your objects prior to the operations. In this case, memento is your savior. So, in a paint application, you can avoid the cost of undoing a paint operation. You can store the list of objects prior to executing the commands. This stored list can be treated as a memento in this case. You can keep this list with the associated commands. I suggest that you read the nice online article at www.developer.com/design/article.php/3720566/Working-With-Design-Patterns-Memento.htm .

    So, an application can use both patterns to support undo operations.

    Finally, you must remember that storing a memento object is mandatory in a memento pattern, so that you can roll back to a previous state; but in a command pattern, it is not necessary to store the commands. Once you execute a command, its job is done. If you do not support “undo” operations, you may not be interested in storing these commands at all.

     
  5. 10.

    You talked about deep copy after the first implementation. Why do I need that?

    In Chapter 2 (the prototype pattern), I discussed shallow copy and deep copy. You can refer to this discussion for your reference. To answer your question, let’s analyze what is special about deep copy with a simple example. Consider the following example.

     

Shallow Copy vs. Deep Copy in Java

You clone with the clone() method in Java, but at the same time, you need to implement the Cloneable interface (which is a marker interface) because the Java objects that implement this Cloneable interface are only eligible for cloning. The default version of clone()creates a shallow copy. To create the deep copy, you need to override the clone() method .

Key Characteristics of the Following Program

In the following example, you have two classes: Employee and EmpAddress.

The Employee class has three fields: id, name, and EmpAddress. So, you may notice that to form an Employee object, you need to pass an EmpAddress object also. So, in the following example, you will notice the line of code:

Employee emp=new Employee(1,"John",initialAddress);

EmpAddress has only a field called address, which is a String datatype.

In the client code, you create an Employee object emp and then you create another object, empClone, through cloning. So, you will notice the line of code as follows:

Employee empClone=(Employee)emp.clone();

Then you change the field values of the emp object. But as a side effect of this change, the address of empClone object also changes, but this is not wanted.

Implementation

Here is the implementation.
package jdp2e.memento.questions_answers;
class EmpAddress implements Cloneable
{
    String address;
    public EmpAddress(String address)
    {
        this.address=address;
    }
    public String getAddress()
    {
        return address;
    }
    public void setAddress(String address)
    {
        this.address = address;
    }
    @Override
    public String toString()
    {
        return  this.address ;
    }
    @Override
    public Object clone() throws CloneNotSupportedException
    {
        //Shallow Copy
        return super.clone();
    }
}
class Employee implements Cloneable
{
    int id;
    String name;
    EmpAddress empAddress;
    public Employee(int id,String name,EmpAddress empAddress)
    {
        this.id=id;
        this.name=name;
        this.empAddress=empAddress;
    }
    public int getId()
    {
        return id;
    }
    public void setId(int id)
    {
        this.id = id;
    }
    public String getName()
    {
        return name;
    }
    public void setName(String name)
    {
        this.name = name;
    }
    public EmpAddress getAddress()
    {
        return this.empAddress;
    }
    public void setAddress(EmpAddress newAddress)
    {
        this.empAddress=newAddress;
    }
    @Override
    public String toString()
    {
        return "EmpId=" +this.id+ " EmpName="+ this.name+ " EmpAddressName="+ this.empAddress;
    }
    @Override
    public Object clone() throws CloneNotSupportedException
    {
        //Shallow Copy
        return super.clone();
    }
}
public class ShallowVsDeepCopy {
    public static void main(String[] args) throws CloneNotSupportedException  {
        System.out.println("***Shallow vs Deep Copy Demo*** ");
        EmpAddress initialAddress=new EmpAddress("21, abc Road, USA");
        Employee emp=new Employee(1,"John",initialAddress);
        System.out.println("emp1 object is as follows:");
        System.out.println(emp);
        Employee empClone=(Employee)emp.clone();
        System.out.println("empClone object is as follows:");
        System.out.println(empClone);
        System.out.println(" Now changing the name, id and address of the emp object ");
        emp.setId(10);
        emp.setName("Sam");
        emp.empAddress.setAddress("221, xyz Road, Canada");
        System.out.println("Now emp1 object is as follows:");
        System.out.println(emp);
        System.out.println("And emp1Clone object is as follows:");
        System.out.println(empClone);
    }
}

Output

Here is the output.
***Shallow vs Deep Copy Demo***
emp1 object is as follows:
EmpId=1 EmpName=John EmpAddressName=21, abc Road, USA
empClone object is as follows:
EmpId=1 EmpName=John EmpAddressName=21, abc Road, USA
 Now changing the name and id of emp object
Now emp1 object is as follows:
EmpId=10 EmpName=Sam EmpAddressName=221, xyz Road, Canada
And emp1Clone object is as follows:
EmpId=1 EmpName=John EmpAddressName=221, xyz Road, Canada

Analysis

Notice the last line of the output. You see an unwanted side effect. The address of the cloned object is modified due the modification to the emp object. This is because the original object and the cloned object both point to the same address, and they are not 100% disjoined. Figure 19-3 depicts the scenario.
../images/395506_2_En_19_Chapter/395506_2_En_19_Fig3_HTML.jpg
Figure 19-3

Shallow copy

So, now let’s experiment with a deep copy implementation. Let’s modify the clone method of the Employee class as follows.
@Override
    public Object clone() throws CloneNotSupportedException
    {
        //Shallow Copy
        //return super.clone();
        //For deep copy
        Employee employee = (Employee)  super.clone();
        employee.empAddress = (EmpAddress) empAddress.clone();
        return employee;
    }

Modified Output

Here is the modified output.
***Shallow vs Deep Copy Demo***
emp1 object is as follows:
EmpId=1 EmpName=John EmpAddressName=21, abc Road, USA
empClone object is as follows:
EmpId=1 EmpName=John EmpAddressName=21, abc Road, USA
 Now changing the name, id and address of the emp object
Now emp1 object is as follows:
EmpId=10 EmpName=Sam EmpAddressName=221, xyz Road, Canada
And emp1Clone object is as follows:
EmpId=1 EmpName=John EmpAddressName=21, abc Road, USA

Analysis

Notice the last line of the output. Now you do not see the unwanted side effect due to the modification to the emp object. This is because the original object and the cloned object are totally different and independent of each other. Figure 19-4 depicts the scenario.
../images/395506_2_En_19_Chapter/395506_2_En_19_Fig4_HTML.jpg
Figure 19-4

Deep copy

Note

You saw the theoretical parts of a shallow copy and a deep copy in the “Q&A Session” of Chapter 2.

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

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