Gang of Four Behavioural pattern: Memento
Behavioural Pattern
Capture and restore an object's internal state. Without violating encapsulation, capture and externalize an object’s internal state so that the object can be restored to this state later.
To get the code for this example:
git clone https://github.com/spotadev/gangoffour.git
In src/main/java navigate to this package:
com.javaspeak.designpatterns.go4.behavioural.memento
You can run the code from the main method of:
MementoApplication
This example uses the Memento pattern for undo operations. In the Memento pattern there is a Caretaker, Originator and Memento.
The Originator is the class that has state that needs to be rolled back to the previous version. The Caretaker manages the backup and restoration of state to the Originator. The Caretaker asks the Originator for a Memento object that holds a snapshot of its current state. The Caretaker then adds this to a Deque (LIFO queue) it is managing. Later on an undo operation involves taking the last Memento put on the LIFO queue and passing it to the Originator so that the Originator has the information to roll back its state to the last saved version.
In this example we are modelling the saving operation of a text editor. Everytime the document is saved the Caretaker (ApplicationMemento) asks the Originator (DocumentImpl) for a DocumentMemento which it adds to a momentoDeque (LIFO queue). After saving 3 times and printing the saved text to the console an undo operation is called two times restoreFromMemento()).
After each undo operation the document text is printed to the console so you can see that the multiple undo has worked.
package com.javaspeak.designpatterns.go4.behavioural.memento;
import java.util.Deque;
import java.util.LinkedList;
/**
* Text book description:
* <ul>
* Memento: Capture and restore an object's internal state. Without violating encapsulation,
* capture and externalize an object’s internal state so that the object can be restored to this
* state later.
* </ul>
* This example uses the Memento pattern for undo operations. In the Memento pattern there is a
* Caretaker, Originator and Memento.
* <p>
* The Originator is the class that has state that needs to be rolled back to the previous version.
* The Caretaker manages the backup and restoration of state to the Originator. The Caretaker asks
* the Originator for a Memento object that holds a snapshot of its current state. The Caretaker
* then adds this to a Deque (LIFO queue) it is managing. Later on an undo operation involves
* taking the last Memento put on the LIFO queue and passing it to the Originator so that the
* Originator has the information to roll back its state to the last saved version.
* <p>
* In this example we are modelling the saving operation of a text editor. Every time the document
* is saved the Caretaker (ApplicationMemento) asks the Originator (DocumentImpl) for a
* DocumentMemento which it adds to a momentoDeque (LIFO queue). After saving 3 times and printing
* the saved text to the console an undo operation is called two times restoreFromMemento()).
* <p>
* After each undo operation the document text is printed to the console so you can see that the
* multiple undo has worked.
*
* @author John Dickerson - 22 Feb 2020
*/
public class MementoApplication implements Caretaker {
private Document document = new DocumentImpl();
private Deque<Memento> momentoDeque = new LinkedList<Memento>();
@Override
public void saveMemento( Memento memento ) {
momentoDeque.offerLast( memento );
}
@Override
public void restoreFromMemento() {
momentoDeque.removeLast();
Memento lastSavedMemento = momentoDeque.peekLast();
document.restoreFromMemento( lastSavedMemento );
}
/**
* Runs the example application. This application is modelling a the saving operation of a
* text application. Note that the document has: font family, font size and document text
* properties but the DocumentMemento only saves the document text state. This is because we
* only want to undo text edits and not change of font size or font family edits.
* <p>
* Every time we call saveDocumentText(..) on the Document we also call the saveMemento(..)
* method to save the documentText in a DocumentMemento object and place it at the end of a
* deque (LIFO queue).
* <p>
* Every time we ant to undo a text operation we remove the last DocumentMemento off the end of
* the Deque and read the last DocumentMemento still remaining on the the end of the Deque. We
* then use this DocumentMemento to roll back the state of the Document.
*/
public void runExample() {
document.saveFontFamily( "arial" );
document.saveFontSize( 12 );
document.saveDocumentText( "Hello World!" );
saveMemento( document.getMemento() );
document.saveDocumentText( document.getDocumentText() + " Saving a Second Time." );
saveMemento( document.getMemento() );
document.saveDocumentText( document.getDocumentText() + " Saving a Third Time." );
saveMemento( document.getMemento() );
System.out.println( "===============================================" );
System.out.println( document.getDocumentText() );
System.out.println( "===============================================" );
restoreFromMemento();
System.out.println( document.getDocumentText() );
System.out.println( "===============================================" );
restoreFromMemento();
System.out.println( document.getDocumentText() );
System.out.println( "===============================================" );
}
public static void main( String[] args ) {
MementoApplication mementoApplication = new MementoApplication();
mementoApplication.runExample();
}
}
package com.javaspeak.designpatterns.go4.behavioural.memento;
/**
* The class taking care of managing the Mementos is called the Caretaker and should implement
* this interface. In this example the Caretaker is the ApplicationMemento class.
*
* @author John Dickerson - 22 Feb 2020
*/
public interface Caretaker {
/**
* Adds the Memento to the end of a Deque (LIFO queue)
*
* @param memento
* The Memento to add to the end of the Deque (LIFO queue)
*/
public void saveMemento( Memento memento );
/**
* Removes the Memento on the end of the Deque (LIFO queue) and then reads the one still
* remaining on the end of the Deque. Uses this DocumentMemento remaining at the end of the
* queue to roll back the state of the Originator object (DocumentImpl).
*/
public void restoreFromMemento();
}
package com.javaspeak.designpatterns.go4.behavioural.memento;
/**
* Marker interface to mark the DocumentMemento as being a Memento object that saves a snapshot
* state of the Originator (DocumentImpl). The Caretaker (ApplicationMemento) places the
* DocumentMemento on the end of a Deque (LIFO queue) so that it can at a later stage be used in
* an undo operation.
*
* @author John Dickerson - 22 Feb 2020
*/
public interface Memento {
}
package com.javaspeak.designpatterns.go4.behavioural.memento;
/**
* This interface describes document editing functionality such as saving the document text, font
* family and font size.
* <p>
* This interface extends the Originator interface which has additional methods like:
* <pre>
* {@code
* public Memento getMemento();
* public void restoreFromMemento( Memento memento );
* }
* </pre>
* The Document interface is implemented by the Originator. When the getMemento() method is called
* on the Document the Document returns a snapshot of its state in a DocumentMemento object.
* <p>
* When the restoreFromMemento(..) is called on the Document the Document will roll back its state
* to the state encapsulated in the DocumentMemento object.
*
* @author John Dickerson - 22 Feb 2020
*/
public interface Document extends Originator {
public void saveDocumentText( String documentText );
public String getDocumentText();
public void saveFontFamily( String fontFamily );
public String getFontFamily();
public void saveFontSize( int fontSize );
public int getFontSize();
}
package com.javaspeak.designpatterns.go4.behavioural.memento;
/**
* This class describes document editing functionality such as saving the document text, font
* family and font size.
* <p>
* This interface extends the Originator interface which has additional methods like:
* <pre>
* {@code
* public Memento getMemento();
* public void restoreFromMemento( Memento memento );
* }
* </pre>
* The Document interface is implemented by the Originator. When the getMemento() method is called
* on the Document the Document returns a snapshot of its state in a DocumentMemento object.
* <p>
* When the restoreFromMemento(..) is called on the Document the Document will roll back its state
* to the state encapsulated in the DocumentMemento object.
*
* @author John Dickerson - 22 Feb 2020
*/
public class DocumentImpl implements Document {
private String documentText;
private String fontFamily;
private int fontSize;
/**
* Inner class used to create a Memento saving the current state of the document text. Note
* that the final DocumentMemento has a private constructor and no setters so its state cannot
* be modified after it has been created.
*/
final class DocumentMemento implements Memento {
final private String documentMemento;
private DocumentMemento( String document ) {
documentMemento = new String( document );
}
private String getDocumentText() {
return documentMemento;
}
}
// START Document interface methods ============================================================
@Override
public void saveDocumentText( String documentText ) {
this.documentText = documentText;
}
@Override
public String getDocumentText() {
return this.documentText;
}
@Override
public void saveFontFamily( String fontFamily ) {
this.fontFamily = fontFamily;
}
@Override
public String getFontFamily() {
return fontFamily;
}
@Override
public void saveFontSize( int fontSize ) {
this.fontSize = fontSize;
}
@Override
public int getFontSize() {
return fontSize;
}
// END Document interface methods ==============================================================
// START Caretaker interface methods ===========================================================
@Override
public Memento getMemento() {
return new DocumentMemento( documentText );
}
@Override
public void restoreFromMemento( Memento memento ) {
DocumentMemento documentMemento = ( DocumentMemento )memento;
this.documentText = documentMemento.getDocumentText();
}
// END Caretaker interface methods =============================================================
}
package com.javaspeak.designpatterns.go4.behavioural.memento;
/**
* The Originator is the class that needs having it state backed up or restored (DocumentImpl).
* <p>
* Every time the Documents text is saved the Caretaker (ApplicationMemento) calls getMemento() on
* the Document to get a snapshot of its state in the form of a DocumentMemento. The
* DocumentMemento is placed on the end of a Deque (LIFO queue). Every time a text operation needs
* to be undone, the Caretaker retrieves a DocumentMemento from the end of the Deque (LIFO queue)
* and calls the restoreFromMemento(..) method on the Originator (DocumentImpl) so that the Document
* can roll its state back to how it was last time it saved.
*
* @author John Dickerson - 22 Feb 2020
*/
public interface Originator {
/**
* Asks the Originator (DocumentImpl) for a snapshot of its current state encapsulated in a
* Memento (DocumentMemento).
*
* @return Memento (DocumentMemento)
*/
public Memento getMemento();
/**
* Asks the Originator (DocumentImpl) to roll back its state to the same state as specified in
* the Memento (DocumentMemento)
*
* @param memento
* The Memento holding a copy of the state that needs to be set in the Originator
* (DocumentImpl)
*/
public void restoreFromMemento( Memento memento );
}
Back: Gang of Four
Page Author: JD