Memo on the beauty of design patterns: how to optimize memory and time consumption for the backup and recovery of large objects?

The beauty of design patterns by Wang Zheng Study notes

Principle and implementation of memo mode

  • Memo mode, also known as Snapshot mode, is translated into English as Memento Design Patte.
  • Without violating the encapsulation principle, capture the internal state of an object and save the state outside the object so that the object can be restored to its previous state later.
  • The definition of this mode mainly expresses two parts:
    • In part, store replicas for later recovery. This part is easy to understand.
    • The other part is to backup and restore objects without violating the encapsulation principle. This part is not easy to understand.

Examples are given in this paper

  • 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.
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.getText());
      } else if (input.equals(":undo")) {
        InputText snapshot = snapshotsHolder.popSnapshot();
        inputText.setText(snapshot.getText());
      } else {
        snapshotsHolder.pushSnapshot(inputText);
        inputText.append(input);
      }
    }
  }
}
  • The above code does not meet the requirements of object backup and recovery without violating the encapsulation principle:
    • 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 include 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.

restructure

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);
      }
    }
  }
}
  • Define an independent class (Snapshot class) to represent the Snapshot instead of reusing the InputText class. This class only exposes the get() method and does not have any methods such as set() to modify the internal state.
  • In the InputText class, we rename the setText() method to the restoreSnapshot() method, which is more explicit and only used to recover objects.
  • The above code implementation is a typical memo mode code implementation.

How to optimize memory and time consumption?

  • In the example we mentioned earlier, the application scenario uses memos to implement undo operations, and only supports sequential undo, that is, each operation can only undo the last input, and can not skip the input before the last input. 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 little 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.
  • 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 high-frequency backup, Either the consumption of storage (memory or hard disk) or the consumption of time may be unacceptable. To solve this problem, we generally adopt "low frequency full backup" and "high frequency incremental backup" Combined method. When we need to restore the backup to a certain point in time, if we do a full backup at this point in time, we can restore it directly. If there is no corresponding full backup at this time point, we will first find the latest full backup, then use it to restore, and then perform all incremental backups between this full backup and this time point, that is, corresponding operations or data changes. This can reduce the number and frequency of full backup and reduce the consumption of time and memory.

Keywords: Design Pattern

Added by southeastweb on Sat, 18 Dec 2021 16:04:44 +0200