Memento Design Pattern (Behavioral)
Memento is a behavioral design pattern that lets you save and restore the previous state of an object without revealing the details of its implementation.
Problem
Section titled βProblemβImagine youβre creating a text editor app. In addition to simple text editing, your editor can format text, insert images, etc. At some point, you decide to let users undo any operations carried out on the text.
To implement this, you might try to make the editor record its state before performing any operation. However, most objects have their data stored in private fields. Even if you make them public, any change to the editor class might break the classes that copy its state. You end up with a choice: either expose all internal details of the editor (breaking encapsulation) or make it impossible to save its state.
Solution
Section titled βSolutionβThe Memento pattern delegates creating the state snapshots to the actual owner of that state, the Originator object. Instead of other objects trying to copy the state from the βoutside,β the originator itself produces a special object called a Memento.
The memento is a value object that acts as a snapshot of the originatorβs state. It is common practice to make the memento immutable and pass the data only once, via the constructor. Other objects, known as Caretakers, can store these mementos but cannot access or modify the data inside them. When a user triggers an undo, the caretaker gives a memento back to the originator, which uses it to restore its own state.
Real-World Analogy
Section titled βReal-World AnalogyβThink of the Undo feature in a digital camera or a specialized βSave Stateβ in a video game. Before a player enters a boss fight, they save the game. The save file (the Memento) contains all the characterβs stats and progress. The player (the Caretaker) keeps the file but canβt change the stats inside it. If the character dies, the game (the Originator) reads the save file and restores the character to the exact state they were in before the fight.
Structure
Section titled βStructureβ- Originator: The class that can produce snapshots of its own state and restore its state from snapshots.
- Memento: A value object that acts as a snapshot.
- Caretaker: Knows βwhenβ and βwhyβ to capture the originatorβs state, but also βwhenβ the state should be restored. It keeps a history of mementos.
- Encapsulation: In the strict implementation, the mementoβs fields are accessible only to the originator that created it.
Structure (Class Diagram)
Section titled βStructure (Class Diagram)βclassDiagram
class Originator {
-state: string
+save() Memento
+restore(m: Memento)
}
class Memento {
-state: string
+Memento(state: string)
+getState() string
}
class Caretaker {
-originator: Originator
-history: Memento[]
+undo()
}
Originator ..> Memento : creates
Caretaker o-- Memento : stores
Caretaker --> Originator : triggers
Pseudocode
Section titled βPseudocodeβ# The originator holds important state that may change over time.class Editor: private field text, curX, curY, selectionWidth
method setText(text): this.text = text
# Saves the current state inside a memento. method save(): return new Snapshot(this, text, curX, curY, selectionWidth)
# The memento object stores the past state of the editor.class Snapshot: private field editor: Editor private field text, curX, curY, selectionWidth
constructor Snapshot(editor, text, x, y, width): this.editor = editor this.text = text this.curX = x this.curY = y this.selectionWidth = width
# Restores the editor's state. method restore(): editor.setFields(text, curX, curY, selectionWidth)
# The caretaker keeps track of the snapshots.class Command: private field backup: Snapshot
method makeBackup(): backup = editor.save()
method undo(): if (backup != null): backup.restore()Applicability
Section titled βApplicabilityβ- Snapshots: Use the Memento pattern when you want to produce snapshots of the objectβs state to be able to restore a previous state.
- Encapsulation: Use the pattern when direct access to the objectβs fields/getters/setters violates its encapsulation.
How to Implement
Section titled βHow to Implementβ- Determine which class will play the role of the Originator.
- Create the Memento class and declare a set of fields that mirror the state declared inside the originator.
- Make the memento class immutable.
- Add a method for creating mementos to the originator class.
- Add a method for restoring the originatorβs state to its class.
- The Caretaker should know when to ask the originator for a memento and store it in a stack or list.
Pros and Cons
Section titled βPros and ConsβPros:
- You can produce snapshots of an objectβs state without violating its encapsulation.
- You can simplify the originatorβs code by letting the caretaker maintain the history of the originatorβs state.
Cons:
- The app might consume lots of RAM if clients create mementos too often.
- Caretakers must track the originatorβs lifecycle to be able to destroy obsolete mementos.
- Most dynamic programming languages (Python, PHP, JavaScript) canβt guarantee that the state within the memento stays untouched.
Relations with other Design Patterns
Section titled βRelations with other Design Patternsβ- Command and Memento can be used together when implementing βundoβ.
- Memento can be used along with Iterator to capture the current iteration state and roll it back if necessary.
- Sometimes Memento can be used along with Prototype if the state you want to store in history is simple and doesnβt require links to external resources.
Code Examples
Section titled βCode ExamplesβCode Examples (C#)
Section titled βCode Examples (C#)β// Mementopublic class Memento{ public string State { get; }
public Memento(string state) { State = state; }}
// Originatorpublic class Editor{ public string Text { get; set; }
public Memento Save() => new Memento(Text);
public void Restore(Memento memento) { Text = memento.State; }}
// Caretakerpublic class History{ private readonly Stack<Memento> _states = new();
public void Push(Memento m) => _states.Push(m); public Memento Pop() => _states.Pop();}
// Usageclass Program{ static void Main() { var editor = new Editor(); var history = new History();
editor.Text = "Version 1"; history.Push(editor.Save());
editor.Text = "Version 2"; history.Push(editor.Save());
editor.Text = "Version 3";
editor.Restore(history.Pop()); Console.WriteLine(editor.Text); // Version 2
editor.Restore(history.Pop()); Console.WriteLine(editor.Text); // Version 1 }}Code Examples (Python)
Section titled βCode Examples (Python)βfrom __future__ import annotationsfrom datetime import datetimefrom random import samplefrom string import ascii_letters
class Originator(): """ The Originator holds some important state that may change over time. It also defines a method for saving the state inside a memento and another method for restoring the state from it. """ _state = None
def __init__(self, state: str) -> None: self._state = state print(f"Originator: My initial state is: {self._state}")
def do_something(self) -> None: print("Originator: I'm doing something important.") self._state = "".join(sample(ascii_letters, 10)) print(f"Originator: and my state has changed to: {self._state}")
def save(self) -> Memento: return ConcreteMemento(self._state)
def restore(self, memento: Memento) -> None: self._state = memento.get_state() print(f"Originator: My state has changed to: {self._state}")
class Memento(ABC): @abstractmethod def get_name(self) -> str: pass
@abstractmethod def get_date(self) -> str: pass
class ConcreteMemento(Memento): def __init__(self, state: str) -> None: self._state = state self._date = str(datetime.now())[:19]
def get_state(self) -> str: return self._state
def get_name(self) -> str: return f"{self._date} / ({self._state[0:9]}...)"
class Caretaker(): def __init__(self, originator: Originator) -> None: self._mementos = [] self._originator = originator
def backup(self) -> None: print("\nCaretaker: Saving Originator's state...") self._mementos.append(self._originator.save())
def undo(self) -> None: if not len(self._mementos): return
memento = self._mementos.pop() print(f"Caretaker: Restoring state to: {memento.get_name()}") try: self._originator.restore(memento) except Exception: self.undo()
if __name__ == "__main__": originator = Originator("Super-duper-super-puper-awesome.") caretaker = Caretaker(originator)
caretaker.backup() originator.do_something()
caretaker.backup() originator.do_something()
print() caretaker.undo()Code Examples (C++)
Section titled βCode Examples (C++)β#include <iostream>#include <string>#include <stack>
// Mementoclass Memento {private: std::string state;
public: Memento(const std::string& s) : state(s) {}
const std::string& GetState() const { return state; }};
// Originatorclass Editor {private: std::string text;
public: void SetText(const std::string& t) { text = t; }
const std::string& GetText() const { return text; }
Memento Save() const { return Memento(text); }
void Restore(const Memento& memento) { text = memento.GetState(); }};
// Caretakerclass History {private: std::stack<Memento> states;
public: void Push(const Memento& m) { states.push(m); }
Memento Pop() { Memento m = states.top(); states.pop(); return m; }};
// Usageint main() { Editor editor; History history;
editor.SetText("Version 1"); history.Push(editor.Save());
editor.SetText("Version 2"); history.Push(editor.Save());
editor.SetText("Version 3");
editor.Restore(history.Pop()); std::cout << editor.GetText() << std::endl; // Version 2
editor.Restore(history.Pop()); std::cout << editor.GetText() << std::endl; // Version 1
return 0;}Code Examples (Java)
Section titled βCode Examples (Java)β// Mementopublic class Memento {
private final String state;
public Memento(String state) { this.state = state; }
public String getState() { return state; }}
// Originatorpublic class Editor {
private String text;
public void setText(String text) { this.text = text; }
public String getText() { return text; }
public Memento save() { return new Memento(text); }
public void restore(Memento memento) { this.text = memento.getState(); }}
// Caretakerimport java.util.Stack;
public class History {
private final Stack<Memento> states = new Stack<>();
public void push(Memento m) { states.push(m); }
public Memento pop() { return states.pop(); }}
// Usagepublic class Program { public static void main(String[] args) {
Editor editor = new Editor(); History history = new History();
editor.setText("Version 1"); history.push(editor.save());
editor.setText("Version 2"); history.push(editor.save());
editor.setText("Version 3");
editor.restore(history.pop()); System.out.println(editor.getText()); // Version 2
editor.restore(history.pop()); System.out.println(editor.getText()); // Version 1 }}Code Examples (JavaScript)
Section titled βCode Examples (JavaScript)β// Mementoclass Memento { constructor(state) { this.state = state; }}
// Originatorclass Editor { constructor() { this.text = ""; }
save() { return new Memento(this.text); }
restore(memento) { this.text = memento.state; }}
// Caretakerclass History { constructor() { this.states = []; }
push(memento) { this.states.push(memento); }
pop() { return this.states.pop(); }}
// Usageconst editor = new Editor();const history = new History();
editor.text = "Version 1";history.push(editor.save());
editor.text = "Version 2";history.push(editor.save());
editor.text = "Version 3";
editor.restore(history.pop());console.log(editor.text); // Version 2
editor.restore(history.pop());console.log(editor.text); // Version 1Code Examples (Visual Basic)
Section titled βCode Examples (Visual Basic)β' ============================' Memento' ============================Public Class Memento Public ReadOnly Property State As String
Public Sub New(state As String) Me.State = state End SubEnd Class
' ============================' Originator' ============================Public Class Editor Public Property Text As String
Public Function Save() As Memento Return New Memento(Text) End Function
Public Sub Restore(memento As Memento) Text = memento.State End SubEnd Class
' ============================' Caretaker' ============================Public Class History Private ReadOnly _states As New Stack(Of Memento)()
Public Sub Push(m As Memento) _states.Push(m) End Sub
Public Function Pop() As Memento Return _states.Pop() End FunctionEnd Class
' ============================' Usage' ============================Module Program Sub Main() Dim editor As New Editor() Dim history As New History()
editor.Text = "Version 1" history.Push(editor.Save())
editor.Text = "Version 2" history.Push(editor.Save())
editor.Text = "Version 3"
editor.Restore(history.Pop()) Console.WriteLine(editor.Text) ' Version 2
editor.Restore(history.Pop()) Console.WriteLine(editor.Text) ' Version 1 End SubEnd ModuleCode Examples (Rust)
Section titled βCode Examples (Rust)β// Mementostruct Memento { state: String,}
impl Memento { fn new(state: &str) -> Self { Self { state: state.to_string(), } }}
// Originatorstruct Editor { text: String,}
impl Editor { fn new() -> Self { Self { text: String::new(), } }
fn set_text(&mut self, text: &str) { self.text = text.to_string(); }
fn save(&self) -> Memento { Memento::new(&self.text) }
fn restore(&mut self, memento: Memento) { self.text = memento.state; }}
// Caretakerstruct History { states: Vec<Memento>,}
impl History { fn new() -> Self { Self { states: Vec::new() } }
fn push(&mut self, m: Memento) { self.states.push(m); }
fn pop(&mut self) -> Memento { self.states.pop().expect("No mementos left") }}
// Usagefn main() { let mut editor = Editor::new(); let mut history = History::new();
editor.set_text("Version 1"); history.push(editor.save());
editor.set_text("Version 2"); history.push(editor.save());
editor.set_text("Version 3");
editor.restore(history.pop()); println!("{}", editor.text); // Version 2
editor.restore(history.pop()); println!("{}", editor.text); // Version 1}Code Examples (PHP)
Section titled βCode Examples (PHP)β<?php
// Mementoclass Memento{ public string $state;
public function __construct(string $state) { $this->state = $state; }}
// Originatorclass Editor{ public string $text = "";
public function save(): Memento { return new Memento($this->text); }
public function restore(Memento $memento): void { $this->text = $memento->state; }}
// Caretakerclass History{ /** @var Memento[] */ private array $states = [];
public function push(Memento $m): void { $this->states[] = $m; }
public function pop(): Memento { return array_pop($this->states); }}
// Usage$editor = new Editor();$history = new History();
$editor->text = "Version 1";$history->push($editor->save());
$editor->text = "Version 2";$history->push($editor->save());
$editor->text = "Version 3";
$editor->restore($history->pop());echo $editor->text . "\n"; // Version 2
$editor->restore($history->pop());echo $editor->text . "\n"; // Version 1Code Examples (Go)
Section titled βCode Examples (Go)βpackage main
import ( "fmt")
// Mementotype Memento struct { State string}
func NewMemento(state string) *Memento { return &Memento{State: state}}
// Originatortype Editor struct { Text string}
func (e *Editor) Save() *Memento { return NewMemento(e.Text)}
func (e *Editor) Restore(m *Memento) { e.Text = m.State}
// Caretakertype History struct { states []*Memento}
func NewHistory() *History { return &History{states: []*Memento{}}}
func (h *History) Push(m *Memento) { h.states = append(h.states, m)}
func (h *History) Pop() *Memento { if len(h.states) == 0 { return nil } last := h.states[len(h.states)-1] h.states = h.states[:len(h.states)-1] return last}
// Usagefunc main() { editor := &Editor{} history := NewHistory()
editor.Text = "Version 1" history.Push(editor.Save())
editor.Text = "Version 2" history.Push(editor.Save())
editor.Text = "Version 3"
editor.Restore(history.Pop()) fmt.Println(editor.Text) // Version 2
editor.Restore(history.Pop()) fmt.Println(editor.Text) // Version 1}Code Examples (Ruby)
Section titled βCode Examples (Ruby)β# Mementoclass Mementoattr_reader :state
def initialize(state) @state = stateendend
# Originatorclass Editorattr_accessor :text
def save Memento.new(@text)end
def restore(memento) @text = memento.stateendend
# Caretakerclass Historydef initialize @states = []end
def push(memento) @states << mementoend
def pop @states.popendend
# Usageeditor = Editor.newhistory = History.new
editor.text = "Version 1"history.push(editor.save)
editor.text = "Version 2"history.push(editor.save)
editor.text = "Version 3"
editor.restore(history.pop)puts editor.text # Version 2
editor.restore(history.pop)puts editor.text # Version 1Code Examples (Perl)
Section titled βCode Examples (Perl)β#!/usr/bin/perluse strict;use warnings;
############################# Memento############################package Memento;
sub new { my ($class, $state) = @_; return bless { state => $state }, $class;}
sub get_state { my ($self) = @_; return $self->{state};}
############################# Originator############################package Editor;
sub new { my ($class) = @_; return bless { text => "" }, $class;}
sub set_text { my ($self, $text) = @_; $self->{text} = $text;}
sub get_text { my ($self) = @_; return $self->{text};}
sub save { my ($self) = @_; return Memento->new($self->{text});}
sub restore { my ($self, $memento) = @_; $self->{text} = $memento->get_state();}
############################# Caretaker############################package History;
sub new { my ($class) = @_; return bless { states => [] }, $class;}
sub push { my ($self, $memento) = @_; push @{ $self->{states} }, $memento;}
sub pop { my ($self) = @_; return pop @{ $self->{states} };}
############################# Usage############################package main;
my $editor = Editor->new();my $history = History->new();
$editor->set_text("Version 1");$history->push($editor->save());
$editor->set_text("Version 2");$history->push($editor->save());
$editor->set_text("Version 3");
$editor->restore($history->pop());print $editor->get_text(), "\n"; # Version 2
$editor->restore($history->pop());print $editor->get_text(), "\n"; # Version 1Code Examples (TypeScript)
Section titled βCode Examples (TypeScript)β// Mementoclass Memento { state: string;
constructor(state: string) { this.state = state; }}
// Originatorclass Editor { text: string = "";
save(): Memento { return new Memento(this.text); }
restore(memento: Memento): void { this.text = memento.state; }}
// Caretakerclass History { private states: Memento[] = [];
push(m: Memento): void { this.states.push(m); }
pop(): Memento { return this.states.pop()!; }}
// Usageconst editor = new Editor();const history = new History();
editor.text = "Version 1";history.push(editor.save());
editor.text = "Version 2";history.push(editor.save());
editor.text = "Version 3";
editor.restore(history.pop());console.log(editor.text); // Version 2
editor.restore(history.pop());console.log(editor.text); // Version 1