Различные способы реализации шаблона 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, В этом случае должна использоваться пользовательская реализация, запускающая уход. Примитивы можно использовать с автобоксом.

Эта реализация выводит дополнительные затраты времени выполнения и памяти, всегда используя ссылку вместо полей напрямую.

Другие вопросы по тегам