CodeLAB
на главную карта сайта обратная связь
каталог | задачи | паттерны | исходники | стат | форумы | ссылки
 гость
искать в
Главная >> Каталог задач >> Паттерны >> Поведения >> Хранитель (Memento)

<< назад
распечатать обсудить >>


Хранитель (Memento)
реализации: java, количество: 1

Aвтор: this
Дата: 05.10.2007
Просмотров: 43948
Рейтинг: 0/0,0(0)
+
реализации(исходники) +добавить

Имя

«Паттерн
Memento» codelab.ru оригинал codelab.ru источник

Хранитель - паттерн поведения объектов, сохраняющий состояния. Известен также под именем Token (лексема). codelab.ru codelab.ru оригинал источник

Условия, Задача, Назначение

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

Мотивация

Иногда необходимо тем или иным способом зафиксировать (сохранить) внутреннее состояние объекта. Такая потребность возникает, например, при реализации контрольных точек и механизмов отката, позволяющих пользователю отменить пробную операцию или восстановить состояние после ошибки. Его необходимо где-то сохранить, чтобы позднее восстановить в нем объект. codelab.ru оригинал источник codelab.ru
Но обычно объекты инкапсулируют все свое состояние или хотя бы его часть, делая его недоступным для других объектов, так что сохранить состояние извне невозможно. Раскрытие же состояния явилось бы нарушением принципа инкапсуляции и поставило бы под угрозу надежность и расширяемость приложения. codelab.ru источник codelab.ru оригинал

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

codelab.ru codelab.ru источник оригинал

Система разрешения ограничений - хорошо известный способ поддержания связанности между объектами. Ее функции могут выполняться объектом класса ConstraintSolver, который регистрирует вновь создаваемые соединения и генерирует описывающие их математические уравнения. А когда пользователь каким-то образом модифицирует диаграмму, объект решает эти уравнения. Результаты вычислений объект ConstraintSolver использует для перерисовки графики так, чтобы были сохранены все соединения. оригинал codelab.ru codelab.ru источник
codelab.ru codelab.ru источник оригинал
Поддержка отката операций в приложениях не так проста, как может показаться на первый взгляд. Очевидный способ откатить операцию перемещения - это сохранить расстояние между старым и новым положением, а затем переместить объект на такое же расстояние назад. Однако при этом не гарантируется, что все объекты окажутся там же, где находились. Предположим, что в способе расположения соединительной линии есть некоторая свобода. Тогда, переместив прямоугольник на прежнее место, мы можем не добиться желаемого эффекта: оригинал codelab.ru codelab.ru источник

codelab.ru оригинал codelab.ru источник

- как видно, может легко измениться положение соединительной линии (как пример). codelab.ru codelab.ru источник оригинал
оригинал источник codelab.ru codelab.ru
Открытого интерфейса ConstraintSolver иногда не хватает для точного отката всех изменений смежных объектов. Механизм отката должен работать в тесном взаимодействии с ConstraintSolver для восстановления предыдущего состояния, но необходимо также позаботиться о том, чтобы внутренние детали ConstraintSolver не были доступны этому механизму. codelab.ru оригинал codelab.ru источник
codelab.ru источник codelab.ru оригинал
Паттерн хранитель поможет решить данную проблему. Хранитель — это объект, в котором сохраняется внутреннее состояния другого объекта - хозяина хранителя. Для работы механизма отката нужно, чтобы хозяин предоставил хранитель, когда возникнет необходимость записать контрольную точку состояния хозяина.  Только хозяину разрешено помещать в хранитель информацию и извлекать ее оттуда, для других объектов хранитель непрозрачен. codelab.ru codelab.ru источник оригинал
оригинал codelab.ru codelab.ru источник
В примере графического редактора, который обсуждался выше, в роли хозяина может выступать объект ConstraintSolver. Процесс отката характеризуется такой последовательностью событий: codelab.ru codelab.ru оригинал источник
  1. Редактор запрашивает хранитель у объекта ConstraintSolver в процессе выполнения операции перемещения. источник codelab.ru codelab.ru оригинал
  2. ConstraintSolver создает и возвращает хранитель, в данном случае экземпляр класса SolverState. Хранитель SolverState содержит структуры данных, описывающие текущее состояние внутренних уравнений и переменных ConstraintSolver. источник оригинал codelab.ru codelab.ru
  3. Позже, когда пользователь отменяет операцию перемещения, редактор возвращает SolverState объекту ConstraintSolver. codelab.ru источник codelab.ru оригинал
  4. Основываясь на информации, которая хранится в объекте SolverState, ConstraintSolver изменяет свои внутренние структуры, возвращая уравнения и переменные в первоначальное состояние. оригинал источник codelab.ru codelab.ru
Такая организация позволяет объекту ConstraintSolver «знакомить» другие объекты с информацией, которая ему необходима для возврата в предыдущее состояние, не раскрывая в то же время свою структуру и представление. источник codelab.ru codelab.ru оригинал

Признаки применения, использования паттерна Хранитель (Memento)

Используйте паттерн хранитель, когда: оригинал источник codelab.ru codelab.ru
  1. Необходимо сохранить мгновенный снимок состояния объекта (или его части), чтобы впоследствии объект можно было восстановить в том же состоянии. оригинал codelab.ru источник codelab.ru
  2. Прямое получение этого состояния раскрывает детали реализации и нарушает инкапсуляцию объекта. источник codelab.ru оригинал codelab.ru

Решение

оригинал codelab.ru источник codelab.ru

codelab.ru источник оригинал codelab.ru

Участники паттерна Хранитель (Memento)

источник codelab.ru оригинал codelab.ru

  1. Memento (SolverState) – хранитель.
    Сохраняет внутреннее состояние объекта Originator. Объем сохраняемой информации может быть различным и определяется потребностями хозяина.
    Запрещает доступ всем другим объектам, кроме хозяина. По существу, у хранителей есть два интерфейса. «Посыльный» CareTaker «видит» лишь «узкий» интерфейс хранителя - он может только передавать хранителя другим объектам. Напротив, хозяину доступен «широкий» интерфейс, который обеспечивает доступ ко всем данным, необходимым для восстановления в прежнем состоянии. Идеальный вариант - когда только хозяину, создавшему хранитель, открыт доступ к внутреннему состоянию последнего. оригинал источник codelab.ru codelab.ru
  2. Originator (ConstraintSolver) – хозяин.
    Создает хранитель, содержащего снимок текущего внутреннего состояния.
    Использует хранитель для восстановления внутреннего состояния. codelab.ru оригинал codelab.ru источник
  3. CareTaker (механизм отката) – посыльный.
    Отвечает за сохранение хранителя.
    Не производит никаких операций над хранителем и не исследует его внутреннее содержимое. источник codelab.ru оригинал codelab.ru

Схема использования паттерна Хранитель (Memento)

Посыльный (CareTaker) запрашивает хранитель у хозяина, некоторое время держит его codelab.ru оригинал источник codelab.ru

у себя, а затем возвращает хозяину, как видно на представленной диаграмме взаимодействий: codelab.ru источник codelab.ru оригинал

codelab.ru оригинал источник codelab.ru

Иногда этого возвращения не происходит, так как последнему не всегда нужно восстанавливать прежнее состояние. codelab.ru оригинал источник codelab.ru

Хранители пассивны. Только хозяин, создавший хранитель, имеет доступ к информации о состоянии. оригинал codelab.ru источник codelab.ru

Вопросы, касающиеся реализации паттерна Хранитель (Memento)

При реализации паттерна хранитель следует иметь в виду: codelab.ru оригинал codelab.ru источник
  1. Языковую поддержку.
    У хранителей есть 2 интерфейса: «широкий» для хозяев и «узкий» для всех остальных объектов. В идеале язык реализации должен поддерживать два уровня статического контроля доступа. В Java это возможно, если сделать хранителя внутренним классом хозяина, а в C++ для этого потребуется объявить хозяина другом хранителя. Далее в любом случае «широкий» интерфейс хранителя делается закрытым (с помощью ключевого слова private), а открытым (public) остается только «узкий» интерфейс. В случае Java хозяин все же получит доступ к широкому интерфейсу, т.к. весь хранитель, оформленный в виде внутреннего класса, будет ему доступен, как и любой другой объявленный в нем член, в случае C++ доступ будет обеспечен явно, т.к. хозяин будет «другом» хранителя. Т.е. в случае Java получим:
     хранитель в виде внутреннего класса [java]  ссылка
    1. package memento;
    2.  
    3. public class Originator {
    4. /**
    5. * Хранитель, определенный в виде
    6. * внутреннего члена класса Originator-а
    7. */
    8. public class Memento {
    9. /* "Узкий" открытый интерфейс хранителя,
    10. * доступный всем
    11. */
    12. public void CompressMementoState() {
    13. /* ..Оптимизируем размер, занимаемым хранителем.. */
    14. }
    15. public int GetMementoSize() {
    16. int res = 0;
    17. /* ..Вычисляем размер, занимаемый хранителем.. */
    18. return res;
    19. }
    20.  
    21. /* "Широкий" закрытый интерфейс хранителя,
    22. * доступный только хозяину (Originator-у в данном случае)
    23. */
    24. private Memento() {
    25. /* ... */
    26. }
    27.  
    28. private void SetState(State state) {
    29. /* ... */
    30. }
    31.  
    32. private State GetState() {
    33. State res = null;
    34. /* ... */
    35. return res;
    36. }
    37. }
    38.  
    39. /* Методы, интерфейс хозяина
    40. */
    41. public Memento CreateMemento() {
    42. Memento res = null;
    43. /* .. */
    44. return res;
    45. }
    46.  
    47. public void SetMemento(Memento mem) { /* ... */ }
    48. }
    49.  
    50. class State {
    51.  
    52. }
    53.  


    В случае C++:
     хранитель в виде дружеского класса [C++]  ссылка
    1. package memento;
    2.  
    3. class Originator2 {
    4. public:
    5. Memento* CreateMemento();
    6. void SetMemento(const Memento*);
    7. // ...
    8. private:
    9. State* _state; // внутренние структуры данных
    10. // ...
    11. };
    12. class Memento {
    13. public:
    14. // узкий открытый интерфейс
    15. virtual ~Memento();
    16. private:
    17. // закрытые члены доступны только хозяину Originator
    18. friend class Originator;
    19. Memento();
    20. void SetState(State*);
    21. State* GetState() ;
    22. // ...
    23. private:
    24. State* state;
    25. // ...
    26. };
    27.  
    28. class State {
    29.  
    30. }

    оригинал codelab.ru источник codelab.ru
  2. Сохранение инкрементых изменений.
    Если хранители создаются и возвращаются своему хозяину в предсказуемой последовательности, то хранитель может сохранить лишь изменения во внутреннем состоянии хозяина.
    Например, допускающие отмену команды в списке истории могут пользоваться хранителями для восстановления первоначального состояния (см. паттерн команда). Список истории предназначен только для отмены и повтора команд. Это означает, что хранители могут работать лишь с изменениями, сделанными командой, а не с полным состоянием объекта.
    В примере из раздела «Мотивация» объект, отменяющий ограничения, может содержать только такие внутренние структуры, которые изменяются с целью сохранить линию, соединяющую прямоугольники, а не абсолютные позиции всех объектов. codelab.ru codelab.ru источник оригинал

Результаты

Реализация паттерна-хранитель нам дает: codelab.ru codelab.ru оригинал источник
  1. Сохранение границ инкапсуляции.
    Хранитель позволяет избежать раскрытия информации, которой должен распоряжаться только хозяин, но которую тем не менее необходимо хранить вне последнего. Этот паттерн экранирует объекты от потенциально сложного внутреннего устройства хозяина, не изменяя границы инкапсуляции. codelab.ru codelab.ru оригинал источник
  2. Упрощение структуры хозяина.
    При других вариантах дизайна, направленного на сохранение границ инкапсуляции, хозяин хранит внутри себя версии внутреннего состояния, которое запрашивали клиенты. Таким образом, вся ответственность за управление памятью лежит на хозяине. При перекладывании заботы о запрошенном состоянии на клиентов упрощается структура хозяина, а клиентам дается возможность не информировать хозяина о том, что они закончили работу. оригинал codelab.ru codelab.ru источник
  3. Значительные издержки при использовании хранителей.
    С хранителями могут быть связаны заметные издержки, если хозяин должен копировать большой объем информации для занесения в память хранителя или если клиенты создают и возвращают хранителей достаточно часто. Если плата за инкапсуляцию и восстановление состояния хозяина велика, то этот паттерн не всегда подходит. оригинал источник codelab.ru codelab.ru
Недостатки же здесь следующие: оригинал codelab.ru источник codelab.ru
  1. Определение «узкого» и «широкого» интерфейсов.
    В некоторых языках сложно гарантировать, что только хозяин имеет доступ к состоянию хранителя. codelab.ru оригинал источник codelab.ru
  2. Скрытая плата за содержание хранителя.
    Посыльный отвечает за удаление хранителя, однако не располагает информацией о том, какой объем информации о состоянии скрыт в нем. Поэтому нетребовательный к ресурсам посыльный может расходовать очень много памяти при работе с хранителем. оригинал codelab.ru codelab.ru источник

Пример

Представим, что в нашей системе клиенты имеют возможность сделать заказ на некоторый продукт. Выбирается продукт, заполняется форма заказа и далее заказ сохраняется. При этом до того момента как заказ возьмут на исполнение, клиент может редактировать некоторые его параметры. источник codelab.ru codelab.ru оригинал
И вот для большего удоства требуется возможность, чтобы клиент мог «откатывать» свои сделанные изменения в заказе до предыдущего состояния (для простоты – только на 1 уровень назад). оригинал codelab.ru codelab.ru источник
Хороший случай для применения паттерна-хранитель. В качестве Orinator-а у нас будет выступать сама форма заказа (OrderForm), которая в своем составе будет определять класс OrderState (Memento) хранения только тех параметров заказа, которые клиент вправе изменить: OrderForm. источник codelab.ru оригинал codelab.ru
Обратите внимание на то что класс OrderState (Memento) объявлен как внутренний в OrderForm. Это в соответствии с тем, что доступ к внутренним элементам хранителя был только у хозяина (Originator-а). codelab.ru источник codelab.ru оригинал
Соотвественно, когда заказ обновляется, вызывается метод UpdateOrder, создающий новый хранитель OrderState со старыми данными. Когда же пользователь решает отменить сделанные изменения – метод RollbackChanges() возвращает из хранителя это предыдущее состояние. источник codelab.ru оригинал codelab.ru
оригинал codelab.ru источник codelab.ru
источник codelab.ru оригинал codelab.ru
оригинал codelab.ru codelab.ru источник
codelab.ru источник оригинал codelab.ru

Известные применения паттерна Хранитель (Memento)

Пример раздела «Мотивация» был основан на поддержке связанности в каркасе Unidraw с помощью класса CSolver. источник codelab.ru codelab.ru оригинал
В библиотеке QOCA для разрешения ограничений в хранителях содержится информация об изменениях. Клиент может получить хранитель, характеризующий текущее решение системы ограничений. В хранителе находятся только те переменные ограничений, которые были преобразованы со времени последнего решения. Обычно при каждом новом решении изменяется лишь небольшое подмножество переменных Solver. Но этого достаточно, чтобы вернуть Solver к предыдущему решению; для отката к более ранним решениям необходимо иметь все промежуточные хранители. Поэтому передавать хранители в произвольном порядке нельзя. QOCA использует механизм ведения истории для возврата к прежним решениям. источник codelab.ru оригинал codelab.ru
codelab.ru оригинал источник codelab.ru

Родственные паттерны

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


Реализации: java(1)   +добавить реализацию

1) OrderForm.java, code #514[автор:this]


<< назад наверх
распечатать обсудить >>

 
каталог | задачи | паттерны | исходники | стат | форумы | карта сайта | контакты | ссылки 
© 2000-2017 CodeLAB Group
  Все права защищены
Страница сгенерирована за 0.030448 секунд
Количество запросов к БД: 14, gzip: 17.5kb/71.9kb(76%)