Статьи

Пример шаблона Memento Design

Эта статья является частью нашего курса Академии под названием « Шаблоны проектирования Java» .

В этом курсе вы изучите огромное количество шаблонов проектирования и увидите, как они реализуются и используются в Java. Вы поймете причины, почему шаблоны так важны, и узнаете, когда и как применять каждый из них. Проверьте это здесь !

1. Введение

Иногда необходимо записать внутреннее состояние объекта. Это требуется при реализации контрольных точек и механизмов «отмены», которые позволяют пользователям отказаться от предварительных операций или исправить ошибки. Вы должны где-то сохранить информацию о состоянии, чтобы вы могли восстановить объекты до их прежних условий. Но объекты обычно инкапсулируют некоторые или все их состояния, делая его недоступным для других объектов и невозможным для внешнего сохранения. Предоставление этого состояния нарушит инкапсуляцию, что может поставить под угрозу надежность и расширяемость приложения.

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

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

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

Перед реализацией шаблона, давайте узнаем больше о шаблоне дизайна Memento.

2. Что такое шаблон дизайна Memento

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

class_diagram_1

фигура 1

сувенир

  • Хранит внутреннее состояние объекта Originator. Память может хранить столько, сколько необходимо внутреннего состояния отправителя, сколько необходимо по усмотрению отправителя.
  • Защищает от доступа к объектам, кроме автора. У Mementos фактически два интерфейса. Смотритель видит узкий интерфейс к Мементо — он может только передать сувенир другим объектам. Инициатор, напротив, видит широкий интерфейс, который позволяет ему получить доступ ко всем данным, необходимым для восстановления его предыдущего состояния. В идеале, только создатель, создавший сувенир, мог получить доступ к внутреннему состоянию сувенира.

автор

  • Создает сувенир, содержащий снимок его текущего внутреннего состояния.
  • Использует память для восстановления своего внутреннего состояния.

смотритель

  • Отвечает за сохранность сувенира.
  • Никогда не оперирует и не изучает содержимое памятного предмета.

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

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

3. Реализация шаблона дизайна Memento

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package com.javacodegeeks.patterns.mementopattern;
 
public class Originator {
 
    private double x;
    private double y;
 
    private String lastUndoSavepoint;
    CareTaker careTaker;
 
    public Originator(double x, double y,CareTaker careTaker){
        this.x = x;
        this.y = y;
 
        this.careTaker = careTaker;
 
        createSavepoint("INITIAL");
    }
 
    public double getX(){
        return x;
    }
 
    public double getY(){
        return y;
    }
 
    public void setX(double x) {
        this.x = x;
    }
 
    public void setY(double y) {
        this.y = y;
    }
 
    public void createSavepoint(String savepointName){
        careTaker.saveMemento(new Memento(this.x, this.y), savepointName);
        lastUndoSavepoint = savepointName;
    }
 
    public void undo(){
        setOriginatorState(lastUndoSavepoint);
    }
 
    public void undo(String savepointName){
        setOriginatorState(savepointName);
    }
 
    public void undoAll(){
        setOriginatorState("INITIAL");
        careTaker.clearSavepoints();
    }
 
    private void setOriginatorState(String savepointName){
        Memento mem = careTaker.getMemento(savepointName);
        this.x = mem.getX();
        this.y = mem.getY();
    }
 
    @Override
    public String toString(){
        return "X: "+x+", Y: "+y;
    }
 
}

Выше представлен класс Originator , состояние объекта которого должно быть сохранено в памяти. Класс содержит два поля двойного типа x и y , а также принимает ссылку на CareTaker . CareTaker используется для сохранения и извлечения объектов CareTaker которые представляют состояние объекта Originator .

В конструкторе мы сохранили начальное состояние объекта с createSavepoint метода createSavepoint . Этот метод создает объект memento и просит смотрителя позаботиться об объекте. Мы использовали переменную lastUndoSavepoint которая используется для хранения имени ключа последнего сохраненного сувенира для реализации операции undo .

Класс предоставляет три типа операций undo . Метод undo без какого-либо параметра восстанавливает последнее сохраненное состояние, undo с именем точки сохранения в качестве параметра восстанавливает состояние, сохраненное с этим конкретным именем точки сохранения. Метод undoAll просит undoAll осуществляющее уход, очистить все точки сохранения и установить его в начальное состояние (состояние во время создания объекта).

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
package com.javacodegeeks.patterns.mementopattern;
 
public class Memento {
 
    private double x;
    private double y;
 
    public Memento(double x, double y){
        this.x = x;
        this.y = y;
    }
 
    public double getX(){
        return x;
    }
 
    public double getY(){
        return y;
    }
}

Класс Memento используется для хранения состояния Originator и сохраняется хранителем. Класс не имеет никаких методов установки, он используется только для получения состояния объекта.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.javacodegeeks.patterns.mementopattern;
 
import java.util.HashMap;
import java.util.Map;
 
public class CareTaker {
 
    private final Map<String, Memento>savepointStorage = new HashMap<String, Memento>();
 
    public void saveMemento(Memento memento,String savepointName){
        System.out.println("Saving state..."+savepointName);
        savepointStorage.put(savepointName, memento);
    }
 
    public Memento getMemento(String savepointName){
        System.out.println("Undo at ..."+savepointName);
        return savepointStorage.get(savepointName);
    }
 
    public void clearSavepoints(){
        System.out.println("Clearing all save points...");
        savepointStorage.clear();
    }
 
}

Приведенный выше класс является классом, осуществляющим уход, который используется для хранения и предоставления запрошенного объекта сувенира. Класс, содержащий метод saveMemento используется для сохранения объекта memento, метод getMemento используется для возврата объекта memento запроса и метод clearSavepoints который используется для очистки всех clearSavepoints сохранения, и удаляет все сохраненные объекты memento.

Теперь давайте проверим пример.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.javacodegeeks.patterns.mementopattern;
 
public class TestMementoPattern {
 
    public static void main(String[] args) {
 
        CareTaker careTaker = new CareTaker();
        Originator originator = new Originator(5, 10, careTaker);
 
        System.out.println("Default State: "+originator);
 
        originator.setX(originator.getY()*51);
 
        System.out.println("State: "+originator);
        originator.createSavepoint("SAVE1");
 
        originator.setY(originator.getX()/22);
        System.out.println("State: "+originator);
 
        originator.undo();
        System.out.println("State after undo: "+originator);
 
        originator.setX(Math.pow(originator.getX(),3));
        originator.createSavepoint("SAVE2");
        System.out.println("State: "+originator);
        originator.setY(originator.getX()-30);
        originator.createSavepoint("SAVE3");
        System.out.println("State: "+originator);
        originator.setY(originator.getX()/22);
        originator.createSavepoint("SAVE4");
        System.out.println("State: "+originator);
 
        originator.undo("SAVE2");
        System.out.println("Retrieving at: "+originator);
 
        originator.undoAll();
        System.out.println("State after undo all: "+originator);
    }
 
}

Приведенный выше код приведет к следующему выводу.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
Saving state...INITIAL
Default State: X: 5.0, Y: 10.0
State: X: 510.0, Y: 10.0
Saving state...SAVE1
State: X: 510.0, Y: 23.181818181818183
Undo at ...SAVE1
State after undo: X: 510.0, Y: 10.0
Saving state...SAVE2
State: X: 1.32651E8, Y: 10.0
Saving state...SAVE3
State: X: 1.32651E8, Y: 1.3265097E8
Saving state...SAVE4
State: X: 1.32651E8, Y: 6029590.909090909
Undo at ...SAVE2
Retrieving at: X: 1.32651E8, Y: 10.0
Undo at ...INITIAL
Clearing all save points...
State after undo all: X: 5.0, Y: 10.0

В приведенном выше коде мы создали объект CareTaker а затем присвоили его объекту Originator . Затем мы установили значения x и y равными 5 и 10. Затем мы применили некоторую операцию к x и сохранили состояние объекта как «SAVE1».

После еще нескольких операций мы вызвали метод undo чтобы восстановить последнее состояние объекта, которое четко отображается в выходных данных. Затем мы применили некоторые операции и снова сохранили состояния объекта как «SAVE2, SAVE3 и SAVE4».

Затем мы попросили Originator восстановить состояние SAVE2 и вызвать метод undoAll который установил начальное состояние объекта и удалил все точки сохранения.

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

4. Когда использовать Шаблон Memento

Используйте Memento Pattern в следующих случаях:

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

5. Образец Памяти в JDK

  • java.util.Date
  • java.io.Serializable

6. Загрузите исходный код

Это был урок по модели дизайна Memento. Вы можете скачать исходный код здесь: MementoDesignPattern-Project