Поиск по сайту

 

Паттерны » Паттерны поведения » Memento

Паттерн Memento (хранитель)

Назначение паттерна Memento

  • Не нарушая инкапсуляции, паттерн Memento получает и сохраняет за пределами объекта его внутреннее состояние так, чтобы позже можно было восстановить объект в таком же состоянии.
  • Является средством для инкапсуляции "контрольных точек" программы.
  • Паттерн Memento придает операциям "Отмена" (undo) или "Откат" (rollback) статус "полноценного объекта".

Решаемая проблема

Вам нужно восстановить объект обратно в прежнее состояние (те есть выполнить операции "Отмена" или "Откат").

Обсуждение паттерна Memento

Клиент запрашивает Memento (хранителя) у исходного объекта, когда ему необходимо сохранить состояние исходного объекта (установить контрольную точку). Исходный объект инициализирует Memento своим текущим состоянием. Клиент является "посыльным" за Memento, но только исходный объект может сохранять и извлекать информацию из Memento (Memento является "непрозрачным" для клиентов и других объектов). Если клиенту в дальнейшем нужно "откатить" состояние исходного объекта, он передает Memento обратно в исходный объект для его восстановления.

Реализовать возможность выполнения неограниченного числа операций "Отмена" (undo) и "Повтор" (redo) можно с помощью стека объектов Command и стека объектов Memento.

Паттерн проектирования Memento определяет трех различных участников:

  • Originator (хозяин) - объект, умеющий создавать хранителя, а также знающий, как восстановить свое внутреннее состояние из хранителя.
  • Caretaker (смотритель) - объект, который знает, почему и когда хозяин должен сохранять и восстанавливать себя.
  • Memento (хранитель) - "ящик на замке", который пишется и читается хозяином и за которым присматривает смотритель.

Структура паттерна Memento

UML-диаграмма классов паттерна Memento

UML-диаграмма классов паттерна Memento

Пример паттерна 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 

Источник: http://sourcemaking.com/design_patterns/memento/