Definition and structure
Memento mode is also called token mode. GOF defines the memo mode as: without destroying the encapsulation, capture the internal state of an object and save this state outside the object. In this way, the object can be restored to its original saved state later.
When talking about command mode, we once mentioned that using the intermediate command role can realize the functions of undo and redo. From the definition, we can see that the memo mode is specifically used to store the historical state of the object, which is of great help to implement undo and redo functions well. Therefore, in the command mode, undo and redo functions can be implemented in conjunction with the memo mode.
In fact, it is still very simple to just realize the function of saving an object's state at a certain moment - put the attributes to be saved in the object into an object that specializes in the backup management, and call the agreed method to put the backup attributes back into the original object when needed. But you have to take a good look at in order to allow your backup object to access the properties in the original object, does it mean that you have to disclose all the properties of the original private ones of the object in the package? If your approach has broken the encapsulation, then you should consider refactoring.
Memorandum mode is just a general solution proposed by GOF for the issue of "recovering the original state of an object at a certain time". Therefore, in terms of how to maintain encapsulation - due to factors such as language characteristics, the memorandum pattern is not described in detail, but only explains the ideas based on C++.
1) Memento Role: The memorandum role stores the internal status of the "Memorandum Initiator Role". The "Memorandum Initiator Role" determines which internal states of the "Memorandum Initiator Role" are stored as needed. To prevent other objects other than the "Memorandum Initiator Role" from accessing memos. Memos actually have two interfaces. The "Memo Manager Role" can only see the narrow interface provided by the memo - it is invisible for the attributes stored in the Memo role. The "Memorandum Initiator Role" can see a wide interface - you can get the attributes you put into the Memorandum Role.
2) Memo Initiation (Originator) role: The "Memo Initiation Role" creates a memo to record its internal state at the current moment. Use memos to restore internal state when needed.
3) Memorandum Manager (Caretaker) role: responsible for saving memos. The content of the memo cannot be operated or checked.
The class diagram of the memo mode is really simple:
General code implementation
class Originator { private String state = ""; public String getState() { return state; } public void setState(String state) { this.state = state; } public Memento createMemento(){ return new Memento(this.state); } public void restoreMemento(Memento memento){ this.setState(memento.getState()); } } class Memento { private String state = ""; public Memento(String state){ this.state = state; } public String getState() { return state; } public void setState(String state) { this.state = state; } } class Caretaker { private Memento memento; public Memento getMemento(){ return memento; } public void setMemento(Memento memento){ this.memento = memento; } } public class Client { public static void main(String[] args){ Originator originator = new Originator(); originator.setState("Status 1"); System.out.println("Initial state:"+originator.getState()); Caretaker caretaker = new Caretaker(); caretaker.setMemento(originator.createMemento()); originator.setState("Status2"); System.out.println("Status after changing:"+originator.getState()); originator.restoreMemento(caretaker.getMemento()); System.out.println("Status after recovery:"+originator.getState()); } }The code demonstrates an example of single state single backup. The logic is very simple: the state variable in the Originator class needs to be backed up so that it can be restored when needed; in the Memento class, there is also a state variable used to store the temporary state of the state variable in the Originator class; and the Caretaker class is used to manage the memorandum class, which is used to write states or retrieve states into the memorandum object.
Multi-state Multi-Backup Memo
In the example of the general code demonstration, the Originator class has only one state variable that needs to be backed up, while usually, the initiator role is usually a javaBean, there are more than one variables that need to be backed up in the object, and more than one state that needs to be backed up. This is a multi-state multi-backup memo. There are many ways to implement memos. There are many deformations and processing methods for memos. Methods like general code are generally not used. In most cases, memos are multi-state and multiple backups. In fact, it is also very simple to implement multi-state and multi-backup. The most commonly used method is to add a Map container to Memento to store all states, and use a Map container in the Caretaker class to store all backups. Below we give an example of multi-state and multi-backup:
class Originator { private String state1 = ""; private String state2 = ""; private String state3 = ""; public String getState1() { return state1; } public void setState1(String state1) { this.state1 = state1; } public String getState2() { return state2; } public void setState2(String state2) { this.state2 = state2; } public String getState3() { return state3; } public void setState3(String state3) { this.state3 = state3; } public Memento createMemento(){ return new Memento(BeanUtils.backupProp(this)); } public void restoreMemento(Memento memento){ BeanUtils.restoreProp(this, memento.getStateMap()); } public String toString(){ return "state1="+state1+"state2="+state2+"state3="+state3; } } class Memento { private Map<String, Object> stateMap; public Memento(Map<String, Object> map){ this.stateMap = map; } public Map<String, Object> getStateMap() { return stateMap; } public void setStateMap(Map<String, Object> stateMap) { this.stateMap = stateMap; } } class BeanUtils { public static Map<String, Object> backupProp(Object bean){ Map<String, Object> result = new HashMap<String, Object>(); try{ BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass()); PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors(); for(PropertyDescriptor des: descriptors){ String fieldName = des.getName(); Method getter = des.getReadMethod(); Object fieldValue = getter.invoke(bean, new Object[]{}); if(!fieldName.equalsIgnoreCase("class")){ result.put(fieldName, fieldValue); } } } } catch(Exception e){ e.printStackTrace(); } return result; } public static void restoreProp(Object bean, Map<String, Object> propMap){ try { BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass()); PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors(); for(PropertyDescriptor des: descriptors){ String fieldName = des.getName(); if(propMap.containsKey(fieldName)){ Method setter = des.getWriteMethod(); setter.invoke(bean, new Object[]{propMap.get(fieldName)}); } } } catch (Exception e) { e.printStackTrace(); } } } class Caretaker { private Map<String, Memento> memMap = new HashMap<String, Memento>(); public Memento getMemento(String index){ return memMap.get(index); } public void setMemento(String index, Memento memento){ this.memMap.put(index, memento); } } class Client { public static void main(String[] args){ Originator ori = new Originator(); Caretaker caretaker = new Caretaker(); ori.setState1("China"); ori.setState2("Strong"); ori.setState3("Prosperity"); System.out.println("===Initialization status===/n"+ori); caretaker.setMemento("001",ori.createMemento()); ori.setState1("Software"); ori.setState2("Structure"); ori.setState3("Excellent"); System.out.println("===Modified status===/n"+ori); ori.restoreMemento(caretaker.getMemento("001")); System.out.println("===Restored status===/n"+ori); } } The advantages and disadvantages of the memorandum mode and the advantages of the memorandum mode for applicable scenarios are:
When the status in the initiator role changes, it may be a wrong change. We can restore this wrong change using the memo mode.
The status of the backup is saved outside the initiator role, so the initiator role does not need to manage the status of each backup.
Disadvantages of memo mode:
In actual applications, the memorandum mode is multi-state and multi-backup. The state of the initiator role needs to be stored in the memorandum object, which consumes resources relatively severely.
If you need to provide rollback operations, using memo mode is very suitable, such as jdbc transaction operations, Ctrl+Z recovery of text editors, etc.