Design mode: memo mode

Memo mode

Memo mode, also known as Snapshot mode, is translated into English as Memento Design Pattern. The application scenarios are clear and limited. It is mainly used to prevent loss, revocation, recovery, etc. Definition: capture the internal state of an object without violating the encapsulation principle, and save the state outside the object, so as to restore the object to its previous state later.

The definition of this mode mainly expresses two parts:

  • Part I: storing replicas for later recovery
  • Part II: backup and restore objects without violating the encapsulation principle

The question is:

  • Why does storing and restoring replicas violate the encapsulation principle?
  • How does the memo mode not violate the encapsulation principle?

example

Suppose there is such an interview question. I hope you can write a small program that can receive command line input. When the user inputs text, the program will append and store it in the memory text; The user inputs ": list", and the program outputs the contents of memory text in the command line; If the user enters ": undo", the program will undo the last text entered, that is, delete the last text from the memory text. That is:

>hello
>:list
hello
>world
>:list
helloworld
>:undo
>:list
hello

How? As follows:

public class InputText {
	private StringBuilder text = new StringBuilder();
	
	public String getText() {
		return text.toString();
	}
	
	public void append(String input) {
		text.append(input);
	}
	
	public void setText(String text) {
		this.text.replace(0, this.text.length(), text);
	}
}
public class SnapshotHolder {
	private Stack<InputText> snapshots = new Stack<>();
	
	public InputText popSnapshot() {
		return snapshots.pop();
	}

	public void pushSnapshot(InputText inputText) {
		InputText deepClonedInputText = new InputText();
		deepClonedInputText.setText(inputText.getText());
		snapshots.push(deepClonedInputText);
	}
}
public class ApplicationMain {
	public static void main(String[] args) {
		InputText inputText = new InputText();
		SnapshotHolder snapshotsHolder = new SnapshotHolder();
		Scanner scanner = new Scanner(System.in);
		while (scanner.hasNext()) {
			String input = scanner.next();
			if (input.equals(":list")) {
				System.out.println(inputText.toString());
			} else if (input.equals(":undo")) {
				InputText snapshot = snapshotsHolder.popSnapshot();
				inputText.setText(snapshot.getText());
			} else {
				snapshotsHolder.pushSnapshot(inputText);
				inputText.append(input);
			}
		}
	}
}

In fact, the implementation of memo mode is very flexible and there is no fixed implementation method. The code implementation may be different under different business requirements and different programming languages. The above code has basically realized the most basic memo function. However, if we go deep into it, there are still some problems to be solved. That is the second point defined above: backup and restore objects without violating the encapsulation principle. The above code does not meet this requirement, which is mainly reflected in the following two aspects:

  • First, in order to recover the InputText object with a snapshot, we defined the setText() function in the InputText class, but this function may be used by other businesses. Therefore, exposing functions that should not be exposed violates the encapsulation principle;
  • Second, the snapshot itself is immutable. Theoretically, it should not contain any set() and other functions to modify the internal state. However, in the above code implementation, the business model of "snapshot" reuses the definition of InputText class, and InputText class itself has a series of functions to modify the internal state. Therefore, using InputText class to represent the snapshot violates the encapsulation principle

To solve the above problems, we make two modifications to the code. First, define an independent class (Snapshot class) to represent snapshots instead of reusing InputText class. This class only exposes the get() method and does not have any methods such as set() to modify the internal state. Second, in the InputText class, we rename the setText() method to the restoreSnapshot() method, which is more explicit and only used to recover objects.

public class InputText {
	private StringBuilder text = new StringBuilder();
	
	public String getText() {
		return text.toString();
	}
	
	public void append(String input) {
		text.append(input);
	}
	
	public Snapshot createSnapshot() {
		return new Snapshot(text.toString());
	}
	
	public void restoreSnapshot(Snapshot snapshot) {
		this.text.replace(0, this.text.length(), snapshot.getText());
	}
}

public class Snapshot {
	private String text;
	
	public Snapshot(String text) {
		this.text = text;
	}
	
	public String getText() {
		return this.text;
	}
}
public class SnapshotHolder {
	private Stack<Snapshot> snapshots = new Stack<>();
	
	public Snapshot popSnapshot() {
		return snapshots.pop();
	}
	
	public void pushSnapshot(Snapshot snapshot) {
		snapshots.push(snapshot);
	}
}
public class ApplicationMain {
	public static void main(String[] args) {
		InputText inputText = new InputText();
		SnapshotHolder snapshotsHolder = new SnapshotHolder();
		Scanner scanner = new Scanner(System.in);
		while (scanner.hasNext()) {
			String input = scanner.next();
			if (input.equals(":list")) {
				System.out.println(inputText.toString());
			} else if (input.equals(":undo")) {
				Snapshot snapshot = snapshotsHolder.popSnapshot();
				inputText.restoreSnapshot(snapshot);
			} else {
				snapshotsHolder.pushSnapshot(inputText.createSnapshot());
				inputText.append(input);
			}
		}
	}
}

In fact, the above code implementation is a typical memo mode code implementation, and it is also the implementation method given in many books.

In addition to the memo mode, there is a very similar concept, "backup", which is more often heard in our usual development. What is the difference and relationship between memo mode and "backup"? In fact, the application scenarios of the two are very similar, both of which are used in loss prevention, recovery, revocation and other scenarios. The difference between them is that memo mode focuses more on code design and implementation, and backup focuses more on architecture design or product design

How to optimize memory and time consumption

If the object data to be backed up is large and the backup frequency is high, the memory occupied by the snapshot will be large and the time-consuming of backup and recovery will be long. How to solve this problem?

There are different solutions in different application scenarios. For example, the application scenario in the above example uses memos to implement revocation operations, and only supports sequential revocation, that is, each operation can only revoke the last input, and can not skip the input before the last input revocation. In the application scenario with such characteristics, in order to save memory, we do not need to store complete text in the snapshot, but only need to record a small amount of information, such as the text length at the moment of obtaining the snapshot, and use this value in combination with the text stored by the InputText object to undo the operation.

Here is another example. Suppose that whenever there are data changes, we need to generate a backup for later recovery. If the data to be backed up is large, such a high-frequency backup, whether it is the consumption of storage (memory or hard disk) or time, may be unacceptable. To solve this problem, we usually use the combination of "low frequency full backup" and "high frequency incremental backup".

The so-called "full backup" is to "take a snapshot" of all data and save it. The so-called "incremental backup" refers to recording each operation or data change.

When we need to restore the backup to a certain point in time, if there is a full backup at this point in time, we can restore it directly. If there is no corresponding full backup at this point in time, we will find the latest full backup and use it to recover, and then perform all incremental backups between this full backup and this point in time, that is, corresponding operations or data changes, so as to reduce the number and frequency of full backup and reduce the consumption of time and memory.

summary

Memo mode is also called snapshot mode. Specifically, it captures the internal state of an object without violating the encapsulation principle, and saves the state outside the object, so as to restore the object to its previous state. The definition of this pattern expresses two parts: one is to store copies for later recovery; The other part is to backup and restore objects without violating the encapsulation principle.

The application scenarios of memo mode are also relatively clear and limited, which are mainly used to prevent loss, revocation, recovery, etc. It is very similar to what we often call "backup". The main difference between the two is that memo mode focuses more on code design and implementation, and backup focuses more on architecture design or product design.

For the backup of large objects, the storage space occupied by backup will be large, and the time-consuming of backup and recovery will be long. To solve this problem, different business scenarios have different processing methods. For example, only the necessary recovery information is backed up and restored in combination with the latest data; Another example is the combination of full backup and incremental backup, low-frequency full backup and high-frequency incremental backup.

Keywords: Java

Added by joopeon on Thu, 06 Jan 2022 09:33:33 +0200