An unlimited "undo" and "redo" capability can be readily implemented with a stack of Command objects and a stack of Memento objects.
The Engine knows how to save and restore itself. The Client knows when.
The Engine stores its state in a Memento "lock box", and puts it on deposit with the Client. The Client subsequently decides if, and when, the Memento is returned to the Engine.
| Before | After | |
|---|---|---|
tbd |
// "check point" and "roll back" a Stack
class Memento {
friend class Stack;
int *m_items, m_num;
Memento( int* arr, int num ) {
m_items = new int[m_num = num];
for (int i=0; i < m_num; i++)
m_items[i] = arr[i];
}
public:
~Memento() { delete m_items; }
};
class Stack {
int m_items[10], m_sp;
public:
Stack() { m_sp = -1; }
void push( int in ) { m_items[++m_sp] = in; }
int pop() { return m_items[m_sp--]; }
bool is_empty() { return (m_sp == -1); }
Memento* check_point() {
return new Memento( m_items, m_sp+1 );
}
void roll_back( Memento* mem ) {
m_sp = mem->m_num-1;
for (int i=0; i < mem->m_num; ++i)
m_items[i] = mem->m_items[i];
}
friend ostream& operator<< ( ostream& os, const Stack& s ) {
string buf( "[ " );
for (int i=0; i < s.m_sp+1; i++) {
buf += s.m_items[i]+48;
buf += ' ';
}
buf += ']';
return os << buf;
}
};
int main( void ) {
Stack s;
for (int i=0; i < 5; i++)
s.push( i );
cout << "stack is " << s << '\n';
Memento* first = s.check_point();
for (int i=5; i < 10; i++)
s.push( i );
cout << "stack is " << s << '\n';
Memento* second = s.check_point();
cout << "popping stack: ";
while ( ! s.is_empty())
cout << s.pop() << ' ';
cout << '\n';
cout << "stack is " << s << '\n';
s.roll_back( second );
cout << "second is " << s << '\n';
s.roll_back( first );
cout << "first is " << s << '\n';
cout << "popping stack: ";
while ( ! s.is_empty())
cout << s.pop() << ' ';
cout << '\n';
delete first; delete second;
}
// stack is [ 0 1 2 3 4 ]
// stack is [ 0 1 2 3 4 5 6 7 8 9 ]
// popping stack: 9 8 7 6 5 4 3 2 1 0
// stack is [ ]
// second is [ 0 1 2 3 4 5 6 7 8 9 ]
// first is [ 0 1 2 3 4 ]
// popping stack: 4 3 2 1 0
|
Command can use Memento to maintain the state required for an undo operation. [GoF, p242]
Memento is often used in conjunction with Iterator. An Iterator can use a Memento to capture the state of an iteration. The Iterator stores the Memento internally. [GoF, p271]