«1. Обзор

В этом уроке мы узнаем, что такое шаблон проектирования Memento и как его использовать.

Сначала немного теории. Затем мы создадим пример, в котором проиллюстрируем использование шаблона.

2. Что такое шаблон проектирования Memento?

Шаблон проектирования Memento, описанный «Бандой четырех» в их книге, представляет собой поведенческий шаблон проектирования. Шаблон проектирования Memento предлагает решение для реализации невыполнимых действий. Мы можем сделать это, сохранив состояние объекта в данный момент и восстановив его, если действия, выполненные с тех пор, необходимо отменить.

На практике объект, состояние которого нужно сохранить, называется Первоисточником. Смотритель — это объект, запускающий сохранение и восстановление состояния, которое называется Мементо.

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

Давайте посмотрим на краткую диаграмму классов, иллюстрирующую, как различные объекты взаимодействуют друг с другом:

Как мы видим, Создатель может производить и потреблять Сувениры. Между тем, смотритель только сохраняет состояние перед его восстановлением. Внутреннее представление Создателя скрыто от внешнего мира.

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

3. Когда использовать шаблон проектирования Memento?

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

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

4.1. Исходный пример

Давайте теперь рассмотрим пример шаблона проектирования Memento. Давайте представим, что у нас есть текстовый редактор:

У него есть текстовое окно, в котором содержится текущий введенный текст, и есть возможность добавить дополнительный текст:

public class TextEditor {

    private TextWindow textWindow;

    public TextEditor(TextWindow textWindow) {
        this.textWindow = textWindow;
    }
}

4.2. Memento

public class TextWindow {

    private StringBuilder currentText;

    public TextWindow() {
        this.currentText = new StringBuilder();
    }

    public void addText(String text) {
        currentText.append(text);
    }
}

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

Для этого мы воспользуемся шаблоном проектирования Memento. Во-первых, мы создадим объект, содержащий текущий текст окна:

Этот объект — наш сувенир. Как мы видим, мы решили использовать String вместо StringBuilder, чтобы предотвратить любое обновление текущего текста посторонними.

public class TextWindowState {

    private String text;

    public TextWindowState(String text) {
        this.text = text;
    }

    public String getText() {
        return text;
    }
}

4.3. Создатель

После этого мы должны предоставить классу TextWindow методы для создания и использования объекта Memento, сделав TextWindow нашим Создателем:

Метод save() позволяет нам создать объект, в то время как метод restore() использует его для восстановления предыдущего состояния.

public TextWindowState save() {
    return new TextWindowState(wholeText.toString());
}

public void restore(TextWindowState save) {
    currentText = new StringBuilder(save.getText());
}

4.4. Смотритель

Наконец, мы должны обновить наш класс TextEditor. Как Хранитель, он будет сохранять состояние Создателя и запрашивать его восстановление при необходимости:

4.5. Тестирование решения

private TextWindowState savedTextWindow;

public void hitSave() {
    savedTextWindow = textWindow.save();
}

public void hitUndo() {
    textWindow.restore(savedTextWindow);
}

Давайте посмотрим, работает ли оно на тестовом прогоне. Представьте, что мы добавляем какой-то текст в наш редактор, сохраняем его, затем добавляем еще и, наконец, отменяем. Для этого мы добавим метод print() в наш TextEditor, который возвращает строку текущего текста:

Как мы видим, последнее предложение не является частью текущего текста, т.к. Memento был сохранен перед его добавлением.

TextEditor textEditor = new TextEditor(new TextWindow());
textEditor.write("The Memento Design Pattern\n");
textEditor.write("How to implement it in Java?\n");
textEditor.hitSave();
 
textEditor.write("Buy milk and eggs before coming home\n");
 
textEditor.hitUndo();

assertThat(textEditor.print()).isEqualTo("The Memento Design Pattern\nHow to implement it in Java?\n");

5. Заключение

«В этой короткой статье мы объяснили шаблон проектирования Memento и для чего его можно использовать. Мы также рассмотрели пример, иллюстрирующий его использование в простом текстовом редакторе.

Полный код, используемый в этой статье, можно найти на GitHub.

«