Меню
Главная
Авторизация/Регистрация
 
Главная arrow Информатика arrow Введение в архитектуру программного обеспечения

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

Название и классификация паттерна

Хранитель — паттерн поведения объектов.

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

Не нарушая инкапсуляции, паттерн Memento получает и сохраняет за пределами объекта его внутреннее состояние так, чтобы позже можно было восстановить объект в таком же состоянии.

Является средством для инкапсуляции «контрольных точек» программы.

Паттерн Memento придает операциям «Отмена» (undo) или «Откат» (rollback) статус «полноценного объекта».

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

Иногда необходимо тем или иным способом зафиксировать внутреннее состояние объекта. Такая потребность возникает, например,

при реализации контрольных точек и механизмов отката, позволяющих пользователю отменить пробную операцию или восстановить состояние после ошибки. Его необходимо где-то сохранить, чтобы позднее восстановить в нем объект.

Рассмотрим графический редактор. Пользователь может соединить два прямоугольника линией, и они останутся в таком положении при любых перемещениях. Редактор сам перерисовывает линию, сохраняя связанность конфигурации.

Паттерн Хранитель поможет решить данную проблему. Хранитель — это объект, в котором сохраняется внутреннее состояние другого объекта — хозяина Хранителя. Для работы механизма отката нужно, чтобы хозяин предоставил Хранитель, когда возникнет необходимость записать контрольную точку состояния хозяина.

Только хозяину разрешено помещать в Хранитель информацию и извлекать ее оттуда, для других объектов Хранитель непрозрачен.

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

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

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

Структура паттерна Хранитель показана на рис. 69.

UML-диаграмма паттерна Хранитель

Рис. 69. UML-диаграмма паттерна Хранитель

Участники

Originator (хозяин):

  • • создает Хранитель, содержащий снимок текущего внутреннего состояния;
  • • использует Хранитель для восстановления внутреннего состояния.

Caretaker (механизм отката) — посыльный:

  • • отвечает за сохранение Хранителя;
  • • не производит никаких операций над Хранителем и не исследует его внутреннее содержимое.

Memento (Хранитель):

  • • сохраняет внутреннее состояние объекта Originator. Объем сохраняемой информации может быть различным и определяется потребностями хозяина;
  • • запрещает доступ всем другим объектам, кроме хозяина. По существу, у Хранителей есть два интерфейса. Caretaker «видит» лишь «узкий» интерфейс Хранителя — он может только передавать Хранителя другим объектам. Напротив, хозяину доступен «широкий» интерфейс, который обеспечивает доступ ко всем данным, необходимым для восстановления в прежнем состоянии. Идеальный вариант — когда только хозяину, создавшему Хранитель, открыт доступ к внутреннему состоянию последнего.

Особенности паттерна Хранитель

Сохранение границ инкапсуляции. Хранитель позволяет избежать раскрытия информации, которой должен распоряжаться только хозяин, но которую тем не менее необходимо хранить вне последнего. Этот паттерн экранирует объекты от потенциально сложного внутреннего устройства хозяина, не изменяя границы инкапсуляции.

Упрощение структуры хозяина. При других вариантах дизайна, направленного на сохранение границ инкапсуляции, хозяин хранит внутри себя версии внутреннего состояния, которое запрашивали клиенты. Таким образом, вся ответственность за управление памятью лежит на хозяине. При перекладывании заботы о запрошенном состоянии на клиентов упрощается структура хозяина, а клиентам дается возможность не информировать хозяина о том, что они закончили работу.

Значительные издержки при использовании Хранителей. С Хранителями могут быть связаны заметные издержки, если хозяин должен копировать большой объем информации для занесения в память Хранителя или если клиенты создают и возвращают Хранителей достаточно часто.

Определение «узкого» и «широкого» интерфейсов. В некоторых языках сложно гарантировать, что только хозяин имеет доступ к состоянию Хранителя.

Скрытая плата за содержание Хранителя. Посыльный отвечает за удаление Хранителя, однако не располагает информацией о том, какой объем информации о состоянии скрыт в нем. Поэтому нетребовательный к ресурсам посыльный может расходовать очень много памяти при работе с Хранителем.

Пример паттерна Memento

Этот паттерн часто используется механиками-любителями для ремонта тормозов на своих автомобилях. Колеса удаляются с обеих сторон, чтобы сделать видимыми правые и левые тормоза. При этом разбирается только одна сторона, другая же служит напоминанием (Memento) о том, как части тормозной системы собраны вместе. Только после того, как завершена работа с одной стороны, разбирается другая сторона. При этом в качестве Memento выступает уже первая сторона.

Реализация паттерна Memento

Memento — это объект, хранящий «снимок» внутреннего состояния другого объекта. Memento может использоваться для поддержки «многоуровневой» отмены действий паттерна Command. В этом примере перед выполнением команды по изменению объекта Number текущее состояние этого объекта сохраняется в статическом списке истории Хранителей Memento, а сама Команда сохраняется в статическом списке истории команд. Undo() просто восстанавливает состояние объекта Number, получаемое из списка истории Хранителей. Redo() использует список истории команд. Обратите внимание, Memento «открыт» для Number.

#include

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:

typedefvoid(Number:: *Action)(); Command(Number *receiver, Action action) {

_receiver = receiver;

_action = action;

}

virtual void execute()

{

_mementoList[_numCommands] = _receiver->createMemento(); _commandList[_numCommands] = this; if (_numCommands > _highWater)

_high Water = _numCommands;

_numCommands++;

(_receiver-> *_action)();

}

static void undo()

{

if (_numCommands == 0)

{

cout << "*** Attempt to run off the end!! ***" << endl; return;

}

_commandList[_numCommands - l]->_receiver->reinstateMemento (_mementoList[_numCommands - 11);

_numCommands—;

}

void static redo()

{

if (_numCommands > _highWater)

{

cout << "*** Attempt to run off the end!! ***" << endl; return;

}

  • (_commandList[_numCommands|->_receiver-@KOD = >*(_ command List[_numCommands |
  • ->_action))();

_numCommands++;

}

protected:

Number *_receiver;

Action _action;

static Command *_commandList[20|; static Memento *_mementoList[20]; static int _numCommands; static int high Water;

Command * Command: :_command List [ ];

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[l] = new Command(object, &Number::dubble); commands[2] = new Command(object, &Number::half);

cout « "Exit[0], Doublet 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[l], Half[2], Undof3], Redo[4|: cin >> i;

}

}

Вывод программы:

Integer: 11

Exit[0], Doublet 1], Half[2], Undo[3], Redo[4]: 2 5

Exit[0], Doublet 1], Half[2], Undo[3], Redo[4]: 1 10

Exit[0], Doublet 1], Half[2], Undo[3], Redo[4]: 2 5

Exit[0], Doublet 1], Half[2], Undo[3], Redo[4]: 3 10

ExitfO], Doublet 1], Halff2], Undo[3], Redo[4]: 3 5

Exit[0], Double! 1], Half[2], Undo[3], Redo[4]: 3

11

Exit[01, Double! 1], Halff2], Undo[3], Redo[4|: 3

  • *** Attempt to run off the end!! ***
  • 11

Exit[0], Double! 1], Halff2], Undo[3], Redo[4|: 4 5

Exit[0], Double! 1], Half[2], Undo[3], Redo[4|: 4 10

ExitfO], Double! 1], Halff2], Undo[3], Redo[4|: 4 5

Exit[0], Double! 1], Half[2], Undo[3], Redo[4|: 4

*** Attempt to run off the end!! ***

Особенности паттерна Memento

Паттерны Command и Memento определяют объекты «волшебная палочка», которые передаются от одного владельца к другому и используются позднее. В Command такой «волшебной палочкой» является запрос; в Memento — внутреннее состояние объекта в некоторый момент времени. Полиморфизм важен для Command, но не важен для Memento потому, что интерфейс Memento настолько «узкий», что его можно передавать как значение.

Command может использовать Memento для сохранения состояния, необходимого для выполнения отмены действий.

Memento часто используется совместно с Iterator. Iterator может использовать Memento для сохранения состояния итерации.

 
Если Вы заметили ошибку в тексте выделите слово и нажмите Shift + Enter
< Пред   СОДЕРЖАНИЕ   След >
 

Популярные страницы