Эта статья является частью нашего курса Академии под названием « Шаблоны проектирования Java» .
В этом курсе вы изучите огромное количество шаблонов проектирования и увидите, как они реализуются и используются в Java. Вы поймете причины, почему шаблоны так важны, и узнаете, когда и как применять каждый из них. Проверьте это здесь !
Содержание
1. Введение
Иногда необходимо записать внутреннее состояние объекта. Это требуется при реализации контрольных точек и механизмов «отмены», которые позволяют пользователям отказаться от предварительных операций или исправить ошибки. Вы должны где-то сохранить информацию о состоянии, чтобы вы могли восстановить объекты до их прежних условий. Но объекты обычно инкапсулируют некоторые или все их состояния, делая его недоступным для других объектов и невозможным для внешнего сохранения. Предоставление этого состояния нарушит инкапсуляцию, что может поставить под угрозу надежность и расширяемость приложения.
Шаблон Memento может быть использован для достижения этой цели без раскрытия внутренней структуры объекта. Объект, состояние которого необходимо захватить, называется источником.
Чтобы проиллюстрировать использование шаблона Memento, давайте рассмотрим пример. Мы создадим класс, который будет содержать два поля двойного типа, и выполним некоторые математические операции над ним. Мы предоставим пользователям операцию отмены. Если результаты после некоторых операций не будут удовлетворены для пользователя, пользователь может вызвать операцию отмены, которая восстановит состояние объекта до последней сохраненной точки.
Пример также включает в себя механизм точки сохранения, который используется пользователем для сохранения состояния объекта. Мы также предоставим различные операции отмены. Простая отмена восстановит состояние объекта до предыдущей точки сохранения. Отмена с указанной точкой сохранения восстановит это конкретное состояние объекта, а отмена всех удалит все сохраненное состояние объекта и восстановит объект в инициализированном состоянии, когда объект был создан.
Перед реализацией шаблона, давайте узнаем больше о шаблоне дизайна Memento.
2. Что такое шаблон дизайна Memento
Цель шаблона Memento состоит в том, чтобы, не нарушая инкапсуляцию, захватывать и экстернализовать внутреннее состояние объекта, чтобы объект мог быть впоследствии возвращен в это состояние.
сувенир
- Хранит внутреннее состояние объекта 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