21
NovMemento Design Pattern
Understanding Memento Design Pattern
Understanding the Memento Design Pattern is a key concept in software design. It enables you to capture and externalize an object's internal state without violating encapsulation, allowing you to restore the object to this state later. This is particularly useful for implementing undo operations in your applications, resulting in a more flexible and manageable system.
In this design pattern tutorial, we will discuss the Memento Design Pattern, including the questions "What is the Memento Design Pattern?" and "When should I use the Memento Design Pattern?" We'll also see examples of the Memento Design Pattern in action. So, let us begin by addressing the question, "What is the Memento Design Pattern?"
What is the Memento Design Pattern?
- The Memento design pattern is a behavioral pattern that is used to capture and restore an object's internal state while maintaining encapsulation.
- It allows you to save and restore an object's state to a prior state, as well as undo or roll back modifications to the object.
- As your program progresses, you may want to save checkpoints and then restore them later.
- The Memento Design pattern's aim is to capture and externalize an object's internal state without violating encapsulation, allowing the object to be restored to this state later.
Memento Design Pattern - UML Diagram & Implementation
The UML class diagram for the implementation of the Memento Design Pattern is given below:
The classes, interfaces, and objects in the above UML class diagram are as follows:
1. Originator
- This component creates and manages an object's state.
- It has methods for setting and retrieving the object's state, as well as the ability to construct Memento objects to store that state.
- The Originator interfaces directly with the Memento to take snapshots of its state and restore it from a snapshot.
2. Memento
- The Memento is an object that stores the Originator's state at a specific point in time.
- It simply allows you to retrieve the state and not modify it directly.
- This ensures the state continues.
3. Caretaker
- The Caretaker is responsible for keeping track of Memento objects.
- It does not know the specifics of the state saved in the Memento, but it can request Mementos from the Originator to preserve or restore the object's state.
Communication between the components
1. Client: The client starts the process by asking the Originator to conduct an activity that may change its state or need it to be saved. For example, the client may initiate an action such as "save state" or "restore state."
2. Originator: The Originator receives a request from the client and either produces a Memento to save its current state (if the request is to save state) or obtains a Memento to restore its prior state.
- If the request is to save the state, The Originator constructs a Memento object to capture the current state and returns it to the customer or caretaker for safekeeping.
- If the request is to restore a state, the Originator fetches the corresponding Memento and recovers its state from the state saved in the Memento.
3. Caretaker: The Caretaker serves as a liaison between the client and the Originator, handling the Memento items.
- If the client wishes to store the state, the Caretaker will receive the Memento from the Originator, and it will cache the Memento for later usage.
- When a client requests state restoration, the Caretaker obtains the necessary Memento from storage and delivers the Memento to the Originator for state restoration.
- The caregiver invokes the createMemento() function on the originator, asking it to pass a memento object.
- At this stage, the originator constructs a memento object, saves its internal state, and transfers it to the caretaker.
- The caretaker keeps the memorial object and executes the operation.
- If the caretaker needs to undo the operation, he or she uses the setMemento() function on the originator, passing the maintained memento object.
- The memento would be accepted by the originator, who would then use it to restore its prior state.
Real Life Example:
DocumentEditor Class (Originator)
- Think of this as the main document you are working on.
- It holds the current content of the document.
- It can save a "snapshot" of the document’s state (create a memento) or go back to a previous version (restore from a memento).
Memento Interface
- This is like a container that holds the saved version of the document.
- It keeps the version safe, so no one can change it directly.
Concrete Memento Class
- This class is like a real saved version of the document.
- It knows how to store and give back the content when needed but doesn’t allow changes to that saved version.
Caretaker Class
- The caretaker is like the undo manager in a text editor.
- It keeps track of saved versions but doesn’t look at or change them.
Key Points
- The Memento pattern is like a save-and-restore feature, letting you save the current state of something (like a document) and go back if needed.
- The DocumentEditor can save versions using the Memento without needing to know how the state is stored.
- It’s useful for adding undo/redo functionality, allowing you to jump back to earlier versions easily.
Example
using System;
using System.Collections.Generic;
// Memento class to store the state
public class Memento
{
private readonly string _state;
public Memento(string state)
{
_state = state;
}
public string GetState()
{
return _state;
}
}
// DocumentEditor (Originator) class
public class DocumentEditor
{
private string _content;
public void SetContent(string content)
{
_content = content;
}
public string GetContent()
{
return _content;
}
// Save the current state to a Memento
public Memento Save()
{
return new Memento(_content);
}
// Restore the state from a Memento
public void Restore(Memento memento)
{
_content = memento.GetState();
}
}
// Caretaker class that manages mementos
public class Caretaker
{
private Memento _memento;
public void SaveState(DocumentEditor editor)
{
_memento = editor.Save(); // Save the current state
}
public void RestoreState(DocumentEditor editor)
{
editor.Restore(_memento); // Restore the saved state
}
}
// Main class to demonstrate the pattern
public class Program
{
public static void Main(string[] args)
{
DocumentEditor editor = new DocumentEditor();
Caretaker caretaker = new Caretaker();
// Set and display initial content
editor.SetContent("Version 1 of the document.");
Console.WriteLine("Current Content: " + editor.GetContent());
// Save the current state
caretaker.SaveState(editor);
// Modify content
editor.SetContent("Version 2 of the document.");
Console.WriteLine("Updated Content: " + editor.GetContent());
// Restore to the previous state
caretaker.RestoreState(editor);
Console.WriteLine("Restored Content: " + editor.GetContent());
}
}
// Memento class to store the state
class Memento {
private final String state; // Holds the document content
public Memento(String state) {
this.state = state;
}
public String getState() {
return state;
}
}
// DocumentEditor (Originator) class
class DocumentEditor {
private String content;
public void setContent(String content) {
this.content = content;
}
public String getContent() {
return content;
}
// Save the current state to a Memento
public Memento save() {
return new Memento(content);
}
// Restore the state from a Memento
public void restore(Memento memento) {
content = memento.getState();
}
}
// Caretaker class that manages mementos
class Caretaker {
private Memento memento;
public void saveState(DocumentEditor editor) {
memento = editor.save(); // Save the current state
}
public void restoreState(DocumentEditor editor) {
editor.restore(memento); // Restore the saved state
}
}
// Main class to demonstrate the pattern
public class Main {
public static void main(String[] args) {
DocumentEditor editor = new DocumentEditor();
Caretaker caretaker = new Caretaker();
// Set and display initial content
editor.setContent("Version 1 of the document.");
System.out.println("Current Content: " + editor.getContent());
// Save the current state
caretaker.saveState(editor);
// Modify content
editor.setContent("Version 2 of the document.");
System.out.println("Updated Content: " + editor.getContent());
// Restore to the previous state
caretaker.restoreState(editor);
System.out.println("Restored Content: " + editor.getContent());
}
}
# Memento class to store the state
class Memento:
def __init__(self, state: str):
self._state = state
def get_state(self) -> str:
return self._state
# DocumentEditor (Originator) class
class DocumentEditor:
def __init__(self):
self._content = ""
def set_content(self, content: str):
self._content = content
def get_content(self) -> str:
return self._content
def save(self) -> Memento:
return Memento(self._content)
def restore(self, memento: Memento):
self._content = memento.get_state()
# Caretaker class that manages mementos
class Caretaker:
def __init__(self):
self._memento = None
def save_state(self, editor: DocumentEditor):
self._memento = editor.save()
def restore_state(self, editor: DocumentEditor):
if self._memento:
editor.restore(self._memento)
# Main function to demonstrate the pattern
def main():
editor = DocumentEditor()
caretaker = Caretaker()
editor.set_content("Version 1 of the document.")
print(f"Current Content: {editor.get_content()}")
caretaker.save_state(editor)
editor.set_content("Version 2 of the document.")
print(f"Updated Content: {editor.get_content()}")
caretaker.restore_state(editor)
print(f"Restored Content: {editor.get_content()}")
if __name__ == "__main__":
main()
// Memento class to store the state
class Memento {
private state: string;
constructor(state: string) {
this.state = state;
}
getState(): string {
return this.state;
}
}
// DocumentEditor (Originator) class
class DocumentEditor {
private content: string = "";
setContent(content: string): void {
this.content = content;
}
getContent(): string {
return this.content;
}
save(): Memento {
return new Memento(this.content);
}
restore(memento: Memento): void {
this.content = memento.getState();
}
}
// Caretaker class that manages mementos
class Caretaker {
private memento: Memento | null = null;
saveState(editor: DocumentEditor): void {
this.memento = editor.save();
}
restoreState(editor: DocumentEditor): void {
if (this.memento) {
editor.restore(this.memento);
}
}
}
// Main function to demonstrate the pattern
function main() {
const editor = new DocumentEditor();
const caretaker = new Caretaker();
editor.setContent("Version 1 of the document.");
console.log("Current Content: " + editor.getContent());
caretaker.saveState(editor);
editor.setContent("Version 2 of the document.");
console.log("Updated Content: " + editor.getContent());
caretaker.restoreState(editor);
console.log("Restored Content: " + editor.getContent());
}
main();
// Memento class to store the state
class Memento {
constructor(state) {
this.state = state;
}
getState() {
return this.state;
}
}
// DocumentEditor (Originator) class
class DocumentEditor {
constructor() {
this.content = "";
}
setContent(content) {
this.content = content;
}
getContent() {
return this.content;
}
save() {
return new Memento(this.content);
}
restore(memento) {
this.content = memento.getState();
}
}
// Caretaker class that manages mementos
class Caretaker {
constructor() {
this.memento = null;
}
saveState(editor) {
this.memento = editor.save();
}
restoreState(editor) {
if (this.memento) {
editor.restore(this.memento);
}
}
}
// Main function to demonstrate the pattern
function main() {
const editor = new DocumentEditor();
const caretaker = new Caretaker();
editor.setContent("Version 1 of the document.");
console.log("Current Content: " + editor.getContent());
caretaker.saveState(editor);
editor.setContent("Version 2 of the document.");
console.log("Updated Content: " + editor.getContent());
caretaker.restoreState(editor);
console.log("Restored Content: " + editor.getContent());
}
main();
Output
Current Content: Version 1 of the document.
Updated Content: Version 2 of the document.
Restored Content: Version 1 of the document.
Explanation
- It is like having a feature that lets you save and restore different versions of a document.
- The DocumentEditor is like a tool that can take a snapshot of its current content and go back to that snapshot when needed.
- The Caretaker is like a manager that keeps track of these snapshots without changing them, ensuring they are available for later use.
- It is like having an undo/redo button that allows you to return to earlier versions of your work easily.
Benefits of Using the Memento Pattern in this Scenario
- Easy State Management: It is like having a straightforward way to save and restore different versions of your work without complicated setups.
- Undo/Redo Functionality: It is like having built-in undo and redo buttons that let you easily go back to the previous states of your document.
- Encapsulation of State: It is like keeping the saved versions secure and separate from the current work so you can manage changes without affecting the saved snapshots.
- Version Control: It is like having a simple method to track and manage changes over time, allowing you to revisit or recover earlier versions as needed.
When to use the Memento Design Pattern
- Undo/Redo Functionality: It is like needing a way to easily go back to previous versions of your work, such as in text editors or drawing applications.
- State Preservation: It is like needing to save and restore the exact state of an object at different points in time without exposing its internal details.
- Simplified State Management: It is like wanting to manage complex state changes in a simple way, where you can store snapshots and revert to them as needed.
- Encapsulation of State: It is like keeping the saved state separate from the current object, ensuring changes are managed securely and cleanly.
When not to use Memento Design Pattern
- Excessive Memory Usage: It is like trying to save too many versions of a document, which can use up a lot of memory and slow down the system.
- Complex State: It is like dealing with a document with many parts or dependencies, where saving and restoring the whole state becomes too complicated.
- High Performance Needs: It is like needing very fast performance where saving and restoring states might add unnecessary overhead.
- Frequent Changes: It is like making changes to a document too often, where constantly saving and managing snapshots becomes impractical and cumbersome.
Summary
The Memento Design Pattern allows you to capture and restore an object's state without breaking encapsulation, making it suitable for implementing undo or rollback features. It is frequently used to manage object states over time, particularly in circumstances requiring snapshotting, undo, or version control. It may not, however, be appropriate for objects with huge or regularly changing states due to significant performance overhead. For mastering such design patterns and improving your architectural skills, consider Scholarhat's Software Architecture and Design Certification Training!