Паттерн Memento (хранитель)
Назначение паттерна Memento
- Не нарушая инкапсуляции, паттерн Memento получает и сохраняет за пределами объекта его внутреннее состояние так, чтобы позже можно было восстановить объект в таком же состоянии.
- Является средством для инкапсуляции "контрольных точек" программы.
- Паттерн Memento придает операциям "Отмена" (undo) или "Откат" (rollback) статус "полноценного объекта".
Решаемая проблема
Вам нужно восстановить объект обратно в прежнее состояние (те есть выполнить операции "Отмена" или "Откат").
Обсуждение паттерна Memento
Клиент запрашивает Memento (хранителя) у исходного объекта, когда ему необходимо сохранить состояние исходного объекта (установить контрольную точку). Исходный объект инициализирует Memento своим текущим состоянием. Клиент является "посыльным" за Memento, но только исходный объект может сохранять и извлекать информацию из Memento (Memento является "непрозрачным" для клиентов и других объектов). Если клиенту в дальнейшем нужно "откатить" состояние исходного объекта, он передает Memento обратно в исходный объект для его восстановления.
Реализовать возможность выполнения неограниченного числа операций "Отмена" (undo) и "Повтор" (redo) можно с помощью стека объектов Command и стека объектов Memento.
Паттерн проектирования Memento определяет трех различных участников:
- Originator (хозяин) - объект, умеющий создавать хранителя, а также знающий, как восстановить свое внутреннее состояние из хранителя.
- Caretaker (смотритель) - объект, который знает, почему и когда хозяин должен сохранять и восстанавливать себя.
- Memento (хранитель) - "ящик на замке", который пишется и читается хозяином и за которым присматривает смотритель.
Структура паттерна Memento
UML-диаграмма классов паттерна Memento
Пример паттерна Memento
Паттерн Memento фиксирует и сохраняет за пределами объекта его внутреннее состояние так, чтобы позже этот объект можно было бы восстановить в таком же состоянии. Этот паттерн часто используется механиками-любителями для ремонта барабанных тормозов на своих автомобилях. Барабаны удаляются с обеих сторон, чтобы сделать видимыми правые и левые тормоза. При этом разбирается только одна сторона, другая же служит напоминанием (Memento) о том, как части тормозной системы тормозной системы собраны вместе. Только после того, как завершена работа с одной стороны, разбирается другая сторона. При этом в качестве Memento выступает уже первая сторона.
Использование паттерна Memento
- Определите роли "смотрителя" и "хозяина".
- Создайте класс Memento и объявите хозяина другом.
- Смотритель знает, когда создавать "контрольную точку" хозяина.
- Хозяин создает хранителя Memento и копирует свое состояние в этот Memento.
- Смотритель сохраняет хранителя Memento (но смотритель не может заглянуть в Memento).
- Смотритель знает, когда нужно "откатить" хозяина.
- Хозяин восстанавливает себя, используя сохраненное в Memento состояние.
Особенности паттерна Memento
- Паттерны Command и Memento определяют объекты "волшебная палочка", которые передаются от одного владельца к другому и используются позднее. В Command такой "волшебной палочкой" является запрос; в Memento - внутреннее состояние объекта в некоторый момент времени. Полиморфизм важен для Command, но не важен для Memento потому, что интерфейс Memento настолько "узкий", что его можно передавать как значение.
- Command может использовать Memento для сохранения состояния, необходимого для выполнения отмены действий.
- Memento часто используется совместно с Iterator. Iterator может использовать Memento для сохранения состояния итерации.
Реализация паттерна Memento
Memento - это объект, хранящий "снимок" внутреннего состояния другого объекта. Memento может использоваться для поддержки "многоуровневой" отмены действий паттерна Command. В этом примере перед выполнением команды по изменению объекта Number
, текущее состояние этого объекта сохраняется в статическом списке истории хранителей Memento
, а сама команда сохраняется в статическом списке истории команд. Undo()
просто восстанавливает состояние объекта Number
, получаемое из списка истории хранителей. Redo()
использует список истории команд. Обратите внимание, Memento
"открыт" для Number
.
#include <iostream.h>
class Number;
class Memento
{
public:
Memento(int val)
{
_state = val;
}
private:
friend class Number;
int _state;
};
class Number
{
public:
Number(int value)
{
_value = value;
}
void dubble()
{
_value = 2 * _value;
}
void half()
{
_value = _value / 2;
}
int getValue()
{
return _value;
}
Memento *createMemento()
{
return new Memento(_value);
}
void reinstateMemento(Memento *mem)
{
_value = mem->_state;
}
private:
int _value;
};
class Command
{
public:
typedef void(Number:: *Action)();
Command(Number *receiver, Action action)
{
_receiver = receiver;
_action = action;
}
virtual void execute()
{
_mementoList[_numCommands] = _receiver->createMemento();
_commandList[_numCommands] = this;
if (_numCommands > _highWater)
_highWater = _numCommands;
_numCommands++;
(_receiver-> *_action)();
}
static void undo()
{
if (_numCommands == 0)
{
cout << "*** Attempt to run off the end!! ***" << endl;
return ;
}
_commandList[_numCommands - 1]->_receiver->reinstateMemento
(_mementoList[_numCommands - 1]);
_numCommands--;
}
void static redo()
{
if (_numCommands > _highWater)
{
cout << "*** Attempt to run off the end!! ***" << endl;
return ;
}
(_commandList[_numCommands]->_receiver->*(_commandList[_numCommands]
->_action))();
_numCommands++;
}
protected:
Number *_receiver;
Action _action;
static Command *_commandList[20];
static Memento *_mementoList[20];
static int _numCommands;
static int _highWater;
};
Command *Command::_commandList[];
Memento *Command::_mementoList[];
int Command::_numCommands = 0;
int Command::_highWater = 0;
int main()
{
int i;
cout << "Integer: ";
cin >> i;
Number *object = new Number(i);
Command *commands[3];
commands[1] = new Command(object, &Number::dubble);
commands[2] = new Command(object, &Number::half);
cout << "Exit[0], Double[1], Half[2], Undo[3], Redo[4]: ";
cin >> i;
while (i)
{
if (i == 3)
Command::undo();
else if (i == 4)
Command::redo();
else
commands[i]->execute();
cout << " " << object->getValue() << endl;
cout << "Exit[0], Double[1], Half[2], Undo[3], Redo[4]: ";
cin >> i;
}
}
Вывод программы:
Integer: 11
Exit[0], Double[1], Half[2], Undo[3], Redo[4]: 2
5
Exit[0], Double[1], Half[2], Undo[3], Redo[4]: 1
10
Exit[0], Double[1], Half[2], Undo[3], Redo[4]: 2
5
Exit[0], Double[1], Half[2], Undo[3], Redo[4]: 3
10
Exit[0], Double[1], Half[2], Undo[3], Redo[4]: 3
5
Exit[0], Double[1], Half[2], Undo[3], Redo[4]: 3
11
Exit[0], Double[1], Half[2], Undo[3], Redo[4]: 3
*** Attempt to run off the end!! ***
11
Exit[0], Double[1], Half[2], Undo[3], Redo[4]: 4
5
Exit[0], Double[1], Half[2], Undo[3], Redo[4]: 4
10
Exit[0], Double[1], Half[2], Undo[3], Redo[4]: 4
5
Exit[0], Double[1], Half[2], Undo[3], Redo[4]: 4
*** Attempt to run off the end!! ***
5