Различные способы реализации шаблона Memento в Java
Я провожу некоторое исследование паттерна Memento, и кажется, что большинство примеров, с которыми я сталкивался, кажутся относительно похожими (сохранение строки в массив и восстановление ее при необходимости), теперь исправьте меня, если я ошибаюсь, но я верю, что метод, который я только что описал, это "клонирование объектов", но каковы другие способы реализации шаблона Memento?
Из того, что я также понял, можно использовать сериализацию, но, кажется, есть серая область, где люди говорят, что это нарушает инкапсуляцию объекта и не является способом реализации в шаблон Memento из-за этого.
Так сможет ли кто-нибудь пролить свет на способы реализации шаблона? Мое исследование создало смесь разных вещей и только запутало все.
Спасибо
2 ответа
Платформа Java Collections определяет Queue
, который может помочь.
Код кандидата:
public final class Memento<T>
{
// List of saved values
private final Queue<T> queue = new ArrayDeque<T>();
// Last entered value, whether it has been saved or not
private T currentValue;
// No initial state, ie currentValue will be null on construction, hence
// no constructor
// Set a value, don't save it
public void set(final T value)
{
currentValue = value;
}
// Persist the currently saved value
public void persist()
{
queue.add(currentValue);
}
// Return the last saved value
public T lastSaved()
{
return queue.element();
}
// Return the last entered value
public T lastEntered()
{
return currentValue;
}
}
В этом коде много чего не хватает, но его легко реализовать:
- вернуться к последнему сохраненному значению;
- нет проверки на нули;
T
не реализуетSerializable
;- удобный метод (например, добавить значение и сделать его последним сохраненным состоянием);
- код не является потокобезопасным!
И т.п.
Образец кода:
public static void main(final String... args)
{
final Memento<String> memento = new Memento<String>();
memento.set("state1");
System.out.println(memento.lastEntered()); // "state1"
memento.persist();
memento.set("state2");
System.out.println(memento.lastEntered()); // "state2"
System.out.println(memento.lastSaved()); // "state1"
}
По сути: это реализация мозговой смерти, которую можно улучшить, но которую можно использовать как основу - расширение ее зависит от ваших потребностей;)
Обычная проблема, которая может прийти с реализациями сувениров, состоит в том, что часто существует потребность во множестве классов, которые представляют внутреннее состояние объектов различного типа. Или реализация memento должна сериализовать состояние объекта в какую-либо другую форму (например, сериализованные объекты Java).
Вот эскиз для реализации сувенира, который не зависит от определенного класса сувенира для класса, состояние которого должно быть зафиксировано для поддержки отмены / повторения.
Сначала нужно ввести основную концепцию:
public interface Reference<T> {
T get();
void set(T value);
}
Это абстракция java.lang.ref.Reference
, потому что этот класс для целей сбора мусора. Но нам нужно использовать это для бизнес-логики. В основном ссылка инкапсулирует поле. Таким образом, они предназначены для использования следующим образом:
public class Person {
private final Reference<String> lastName;
private final Reference<Date> dateOfBirth;
// constructor ...
public String getLastName() {
return lastName.get();
}
public void setLastName(String lastName) {
this.lastName.set(lastName);
}
public Date getDateOfBirt() {
return dateOfBirth.get();
}
public void setDateOfBirth(Date dateOfBirth) {
this.dateOfBirth.set(dateOfBirth);
}
}
Обратите внимание, что создание экземпляров объектов с этими ссылками может быть не таким тривиальным, но мы оставим это здесь.
Теперь вот детали для реализации памятки:
public interface Caretaker {
void addChange(Change change);
void undo();
void redo();
void checkpoint();
}
public interface Change {
Change createReversal();
void revert();
}
В основном Change
представляет собой одно идентифицируемое изменение состояния идентифицируемого объекта. Change
обратим, вызывая revert
метод и отмена этого изменения могут быть отменены путем изменения изменений, созданных createReversal
метод. Caretaker
накапливает изменения в состояниях объекта через addChange
метод. Призывая undo
а также redo
методы Caretaker
отменяет или восстанавливает (т.е. отменяет изменение изменений) все изменения до достижения следующей контрольной точки. Контрольная точка представляет собой точку, в которой все наблюдаемые изменения будут накапливаться до изменения, которое преобразует все состояния всех измененных объектов из одной действительной конфигурации в другую. Контрольные точки обычно создаются в прошлом или перед действиями. Те созданы через checkpoint
метод.
А теперь вот как использовать Caretaker
с Reference
:
public class ReferenceChange<T> implements Change {
private final Reference<T> reference;
private final T oldValue;
private final T currentReferenceValue;
public ReferenceChange(Reference<T> reference, T oldValue,
T currentReferenceValue) {
super();
this.reference = reference;
this.oldValue = oldValue;
this.currentReferenceValue = currentReferenceValue;
}
@Override
public void revert() {
reference.set(oldValue);
}
@Override
public Change createReversal() {
return new ReferenceChange<T>(reference, currentReferenceValue,
oldValue);
}
}
public class CaretakingReference<T> implements Reference<T> {
private final Reference<T> delegate;
private final Caretaker caretaker;
public CaretakingReference(Reference<T> delegate, Caretaker caretaker) {
super();
this.delegate = delegate;
this.caretaker = caretaker;
}
@Override
public T get() {
return delegate.get();
}
@Override
public void set(T value) {
T oldValue = delegate.get();
delegate.set(value);
caretaker.addChange(new ReferenceChange<T>(delegate, oldValue, value));
}
}
Существует Change
это представляет, как значение Reference
изменился это Change
создается, когда CaretakingReference
установлено. В этой реализации существует потребность во вложенных Reference
в пределах CaretakingReference
реализация, потому что revert
из ReferenceChange
не должен вызывать новый addChange
через CaretakingReference
,
Свойства коллекции не должны использовать Reference
, В этом случае должна использоваться пользовательская реализация, запускающая уход. Примитивы можно использовать с автобоксом.
Эта реализация выводит дополнительные затраты времени выполнения и памяти, всегда используя ссылку вместо полей напрямую.