Wednesday, February 9, 2011

Behavioral Patterns - Momento Pattern

Definition

To record an object internal state without violating encapsulation and reclaim it later without knowledge of the original object. 



Intent

  • Without violating encapsulation, capture and externalize an object’s internal state so that the object can be returned to this state later.
  • A magic cookie that encapsulates a “check point” capability.
  • Promote undo or rollback to full object status.

Problem

Need to restore an object back to its previous state (e.g. “undo” or “rollback” operations).

Structure

Memento scheme



Where to use & benefits

  • Let some info in an object to be available by another object by using default access control.
  • Save some info for later uses.
  • Need undo/redo features.
  • Used in database transaction.
  • Related patterns include
    • Command, which supports undo or redo features, whereas a memento keeps state of an object.
    • Iterator, which provides a way to access the elements of an aggregate object sequentially without exposing its underlying representation, whereas a memento can be used for iteration. 

    Motivation

    It is sometimes necessary to capture the internal state of an object at some point and have the ability to restore the object to that state later in time. Such a case is useful in case of error or failure. Consider the case of a calculator object with an undo operation such a calculator could simply maintain a list of all previous operation that it has performed and thus would be able to restore a previous calculation it has performed. This would cause the calculator object to become larger, more complex, and heavyweight, as the calculator object would have to provide additional undo functionality and should maintain a list of all previous operations. This functionality can be moved out of the calculator class, so that an external (let’s call it undo manager class) can collect the internal state of the calculator and save it. However providing the explicit access to every state variable of the calculator to the restore manager would be impractical and would violate the encapsulation principle.

    Intent

  • The intent of this pattern is to capture the internal state of an object without violating encapsulation and thus providing a mean for restoring the object into initial state when needed.

Implementation

The figure below shows a UML class diagram for the Memento Pattern:
Memento Pattern Implementation - UML Class Diagram
  • Memento
    1. Stores internal state of the Originator object. The state can include any number of state variables.
    2. The Memento must have two interfaces, an interface to the caretaker. This interface must not allow any operations or any access to internal state stored by the memento and thus honors encapsulation. The other interface is to the originator and allows the originator to access any state variables necessary to for the originator to restore previous state.
  • Originator
    1. Creates a memento object capturing the originators internal state.
    2. Use the memento object to restore its previous state.
  • Caretaker
    1. Responsible for keeping the memento.
    2. The memento is opaque to the caretaker, and the caretaker must not operate on it.
A Caretaker would like to perform an operation on the Originator while having the possibility to rollback. The caretaker calls the createMemento() method on the originator asking the originator to pass it a memento object. At this point the originator creates a memento object saving its internal state and passes the memento to the caretaker. The caretaker maintains the memento object and performs the operation. In case of the need to undo the operation, the caretaker calls the setMemento() method on the originator passing the maintained memento object. The originator would accept the memento, using it to restore its previous state.

Discussion

The client requests a Memento from the source object when it needs to checkpoint the source object’s state. The source object initializes the Memento with a characterization of its state. The client is the “care-taker” of the Memento, but only the source object can store and retrieve information from the Memento (the Memento is “opaque” to the client and all other objects). If the client subsequently needs to “rollback” the source object’s state, it hands the Memento back to the source object for reinstatement.
An unlimited “undo” and “redo” capability can be readily implemented with a stack of Command objects and a stack of Memento objects.
The Memento design pattern defines three distinct roles:
  1. Originator - the object that knows how to save itself.
  2. Caretaker - the object that knows why and when the Originator needs to save and restore itself.
  3. Memento - the lock box that is written and read by the Originator, and shepherded by the Caretaker.


Memento in Java

Why read if you can watch?

Watch design patterns video tutorial
Read full article
import java.util.*;

class Memento {
   private String state;

   public Memento(String stateToSave) { state = stateToSave; }
   public String getSavedState() { return state; }
}

class Originator {
   private String state;
   /* lots of memory consumptive private data that is not necessary to define the 
    * state and should thus not be saved. Hence the small memento object. */

   public void set(String state) { 
       System.out.println("Originator: Setting state to "+state);
       this.state = state; 
   }

   public Memento saveToMemento() { 
       System.out.println("Originator: Saving to Memento.");
       return new Memento(state); 
   }
   public void restoreFromMemento(Memento m) {
       state = m.getSavedState(); 
       System.out.println("Originator: State after restoring from Memento: "+state);
   }
}   

class Caretaker {
   private ArrayList<Memento> savedStates = new ArrayList<Memento>();

   public void addMemento(Memento m) { savedStates.add(m); }
   public Memento getMemento(int index) { return savedStates.get(index); }
}   

class MementoExample {
   public static void main(String[] args) {
       Caretaker caretaker = new Caretaker();

       Originator originator = new Originator();
       originator.set("State1");
       originator.set("State2");
       caretaker.addMemento( originator.saveToMemento() );
       originator.set("State3");
       caretaker.addMemento( originator.saveToMemento() );
       originator.set("State4");

       originator.restoreFromMemento( caretaker.getMemento(1) );
   }
}

No comments:

Post a Comment