Сувенирный узор
Шаблон «память» — это шаблон проектирования программного обеспечения , который раскрывает частное внутреннее состояние объекта. Одним из примеров того, как это можно использовать, является восстановление объекта в его предыдущее состояние (отмена посредством отката), другой — управление версиями, третий — пользовательская сериализация.
Паттерн «память» реализуется с помощью трёх объектов: создателя , смотрителя и сувенира . Создателем является некоторый объект, имеющий внутреннее состояние. Смотритель собирается что-то сделать с инициатором, но хочет иметь возможность отменить изменение. Смотритель сначала просит у создателя памятный предмет. Затем он выполняет любую операцию (или последовательность операций), которую собирался выполнить. Чтобы вернуться к состоянию до выполнения операций, он возвращает объект-память инициатору. Сувенирный объект сам по себе является непрозрачным объектом (который смотритель не может или не должен изменять). При использовании этого шаблона следует проявлять осторожность, если создатель может изменить другие объекты или ресурсы — шаблон «память» работает с одним объектом.
Классические примеры шаблона «память» включают в себя генератор псевдослучайных чисел (каждый потребитель ГПСЧ служит смотрителем, который может инициализировать ГПСЧ (создатель) с тем же начальным значением (память), чтобы создать идентичную последовательность псевдослучайных чисел) и состояние в конечном автомате.
Обзор
[ редактировать ]Шаблон проектирования Memento — один из двадцати трех известных шаблонов проектирования, включенных в книгу GoF «Шаблоны проектирования» 1994 года , которые описывают, как решать повторяющиеся проблемы проектирования для разработки гибкого и многократно используемого объектно-ориентированного программного обеспечения, то есть объектов, которые легче внедрять, изменять, тестировать и повторно использовать. Шаблон Memento был создан Ноем Томпсоном, Дэвидом Эспириту и доктором Дрю Клинкенбердом для первых продуктов HP.
Какие проблемы может решить шаблон проектирования Memento?
[ редактировать ]- Внутреннее состояние объекта должно быть сохранено снаружи, чтобы позже объект можно было восстановить в это состояние.
- Инкапсуляция объекта не должна нарушаться.
Проблема в том, что хорошо спроектированный объект инкапсулирован так, что его представление (структура данных) скрыт внутри объекта и не может быть доступен снаружи объекта.
Какое решение описывает шаблон проектирования Memento?
[ редактировать ]Сделать объект (создатель) ответственным за
- сохранение своего внутреннего состояния в объекте (памяти) и
- восстановление предыдущего состояния из объекта (памяти).
Доступ к нему имеет только автор, создавший сувенир.
Клиент (опекун) может запросить сувенир у отправителя (для сохранения внутреннего состояния отправителя) и передать сувенир обратно отправителю (для восстановления предыдущего состояния).
Это позволяет сохранить и восстановить внутреннее состояние оригинатора, не нарушая его инкапсуляцию.
См. также класс UML и диаграмму последовательности ниже.
Структура
[ редактировать ]Класс UML и диаграмма последовательности
[ редактировать ]
На приведенной выше диаграмме классов UML
тот Caretaker
класс относится к Originator
сорт
для экономии( createMemento()
) и восстановление ( restore(memento)
) внутреннее состояние оригинатора.
Originator
класс реализует
(1) createMemento()
путем создания и возврата Memento
объект, который хранит текущее внутреннее состояние отправителя
и
(2) restore(memento)
восстанавливая состояние из переданного в Memento
объект.
Диаграмма UML последовательности
показывает взаимодействие во время выполнения:
(1) Сохранение внутреннего состояния отправителя: Caretaker
вызовы объектов createMemento()
на Originator
объект,
что создает Memento
объект, сохраняет
его текущее внутреннее состояние ( setState()
) и возвращает Memento
к Caretaker
.
(2) Восстановление внутреннего состояния отправителя: Caretaker
звонки restore(memento)
на Originator
объект и указывает Memento
объект, хранящий состояние, которое необходимо восстановить. Originator
получает состояние ( getState()
) из Memento
установить свое собственное состояние.
Пример Java
[ редактировать ]
Следующая программа на Java иллюстрирует использование шаблона «память» для «отмены».
import java.util.List;
import java.util.ArrayList;
class Originator {
private String state;
// The class could also contain additional data that is not part of the
// state saved in the memento..
public void set(String state) {
this.state = state;
System.out.println("Originator: Setting state to " + state);
}
public Memento saveToMemento() {
System.out.println("Originator: Saving to Memento.");
return new Memento(this.state);
}
public void restoreFromMemento(Memento memento) {
this.state = memento.getSavedState();
System.out.println("Originator: State after restoring from Memento: " + state);
}
public static class Memento {
private final String state;
public Memento(String stateToSave) {
state = stateToSave;
}
// accessible by outer class only
private String getSavedState() {
return state;
}
}
}
class Caretaker {
public static void main(String[] args) {
List<Originator.Memento> savedStates = new ArrayList<Originator.Memento>();
Originator originator = new Originator();
originator.set("State1");
originator.set("State2");
savedStates.add(originator.saveToMemento());
originator.set("State3");
// We can request multiple mementos, and choose which one to roll back to.
savedStates.add(originator.saveToMemento());
originator.set("State4");
originator.restoreFromMemento(savedStates.get(1));
}
}
Результат:
Originator: Setting state to State1 Originator: Setting state to State2 Originator: Saving to Memento. Originator: Setting state to State3 Originator: Saving to Memento. Originator: Setting state to State4 Originator: State after restoring from Memento: State3
В этом примере в качестве состояния используется строка, которая является неизменяемым объектом в Java. В реальных сценариях государство будет почти всегда является изменяемым объектом, и в этом случае необходимо сделать копию состояния.
Надо сказать, что показанная реализация имеет недостаток: она объявляет внутренний класс. Было бы лучше, если бы эта стратегия сувениров могла применяться более чем к одному автору.
В основном есть три других способа получить Memento:
- Сериализация.
- Класс, объявленный в том же пакете.
- Доступ к объекту также можно получить через прокси-сервер, который может выполнить любую операцию сохранения/восстановления объекта.
пример С#
[ редактировать ]Шаблон «память» позволяет фиксировать внутреннее состояние объекта, не нарушая инкапсуляцию, так что позже при необходимости можно отменить/вернуть изменения. Здесь можно видеть, что объект Memento фактически используется для отмены изменений, внесенных в объект.
class Memento
{
private readonly string savedState;
private Memento(string stateToSave)
{
savedState = stateToSave;
}
public class Originator
{
private string state;
// The class could also contain additional data that is not part of the
// state saved in the memento.
public void Set(string state)
{
Console.WriteLine("Originator: Setting state to " + state);
this.state = state;
}
public Memento SaveToMemento()
{
Console.WriteLine("Originator: Saving to Memento.");
return new Memento(state);
}
public void RestoreFromMemento(Memento memento)
{
state = memento.savedState;
Console.WriteLine("Originator: State after restoring from Memento: " + state);
}
}
}
class Caretaker
{
static void Main(string[] args)
{
var savedStates = new List<Memento>();
var originator = new Memento.Originator();
originator.Set("State1");
originator.Set("State2");
savedStates.Add(originator.SaveToMemento());
originator.Set("State3");
// We can request multiple mementos, and choose which one to roll back to.
savedStates.Add(originator.SaveToMemento());
originator.Set("State4");
originator.RestoreFromMemento(savedStates[1]);
}
}
Пример Python
[ редактировать ]"""
Memento pattern example.
"""
class Originator:
_state = ""
def set(self, state: str) -> None:
print(f"Originator: Setting state to {state}")
self._state = state
def save_to_memento(self) -> "Memento":
return self.Memento(self._state)
def restore_from_memento(self, m: "Memento") -> None:
self._state = m.get_saved_state()
print(f"Originator: State after restoring from Memento: {self._state}")
class Memento:
def __init__(self, state):
self._state = state
def get_saved_state(self):
return self._state
saved_states = []
originator = Originator()
originator.set("State1")
originator.set("State2")
saved_states.append(originator.save_to_memento())
originator.set("State3")
saved_states.append(originator.save_to_memento())
originator.set("State4")
originator.restore_from_memento(saved_states[1])
Пример Javascript
[ редактировать ]// The Memento pattern is used to save and restore the state of an object.
// A memento is a snapshot of an object's state.
var Memento = {// Namespace: Memento
savedState : null, // The saved state of the object.
save : function(state) { // Save the state of an object.
this.savedState = state;
},
restore : function() { // Restore the state of an object.
return this.savedState;
}
};
// The Originator is the object that creates the memento.
// defines a method for saving the state inside a memento.
var Originator = {// Namespace: Originator
state : null, // The state to be stored
// Creates a new originator with an initial state of null
createMemento : function() {
return {
state : this.state // The state is copied to the memento.
};
},
setMemento : function(memento) { // Sets the state of the originator from a memento
this.state = memento.state;
}
};
// The Caretaker stores mementos of the objects and
// provides operations to retrieve them.
var Caretaker = {// Namespace: Caretaker
mementos : [], // The mementos of the objects.
addMemento : function(memento) { // Add a memento to the collection.
this.mementos.push(memento);
},
getMemento : function(index) { // Get a memento from the collection.
return this.mementos[index];
}
};
var action_step = "Foo"; // The action to be executed/the object state to be stored.
var action_step_2 = "Bar"; // The action to be executed/the object state to be stored.
// set the initial state
Originator.state = action_step;
Caretaker.addMemento(Originator.createMemento());// save the state to the history
console.log("Initial State: " + Originator.state); // Foo
// change the state
Originator.state = action_step_2;
Caretaker.addMemento(Originator.createMemento()); // save the state to the history
console.log("State After Change: " + Originator.state); // Bar
// restore the first state - undo
Originator.setMemento(Caretaker.getMemento(0));
console.log("State After Undo: " + Originator.state); // Foo
// restore the second state - redo
Originator.setMemento(Caretaker.getMemento(1));
console.log("State After Redo: " + Originator.state); // Bar
Ссылки
[ редактировать ]- ^ «Шаблон проектирования Memento — структура и сотрудничество» . w3sDesign.com . Проверено 12 августа 2017 г.
Внешние ссылки
[ редактировать ]- Описание шаблона Memento в Ada
- Диаграмма классов Memento UML с примерами кода C# и .NET
- Учебное пособие по созданию исходного кода
- Шаблон проектирования Memento с использованием Java