[Patrón de Diseño y Paradigma: Comportamental] 70 | Patrón Memo: Para el respaldo y recuperación de objetos grandes, ¿cómo optimizar el consumo de memoria y tiempo?

En las últimas dos lecciones, aprendimos sobre el patrón de visitantes. Entre los 23 patrones de diseño, se puede decir que el principio y la implementación del patrón de visitante son los más difíciles de entender, especialmente su implementación de código. Entre ellos, la idea de implementación de usar Single Dispatch para simular Double Dispatch es particularmente difícil de entender. No sé si ya lo has bajado. Si aún no lo has descubierto con claridad, entonces tienes que leerlo varias veces y pensarlo por ti mismo.

Hoy, aprendemos otro patrón de comportamiento, el patrón de memorándum. Este modo no es difícil de entender y dominar. La implementación del código es relativamente flexible y los escenarios de aplicación son relativamente claros y limitados. Se utiliza principalmente para la prevención de pérdidas, la revocación y la recuperación. Por lo tanto, en comparación con las dos lecciones anteriores, el contenido de hoy será relativamente más fácil de aprender.
Sin más preámbulos, ¡comencemos oficialmente el estudio de hoy!

El principio y la implementación del modo memo.

Modo memorándum, también llamado modo instantánea (Snapshot), la traducción al inglés es Memento Design Pattern. En el libro "Patrones de diseño" de GoF, el patrón Memento se define de la siguiente manera:

Captura y externaliza el estado interno de un objeto para que pueda restaurarse más tarde, todo sin violar la encapsulación.

Traducido al chino es: bajo la premisa de no violar el principio de encapsulación, capturar el estado interno de un objeto y guardar este estado fuera del objeto, para que el objeto pueda restaurarse al estado anterior más tarde.

En mi opinión, la definición de este patrón expresa principalmente dos partes. En parte, se almacena una copia para su posterior restauración. Esta parte es fácil de entender. La otra parte es realizar copias de seguridad y restaurar objetos sin violar el principio de encapsulación. Esta parte no es fácil de entender. A continuación, lo explicaré con un ejemplo, especialmente para ayudarlo a resolver estos dos problemas:

  • ¿Por qué almacenar y restaurar una copia violaría el principio de encapsulación?
  • ¿Cómo el modo memo no viola el principio de encapsulación?

Supongamos que hay una pregunta de entrevista de este tipo, espero que escriba un pequeño programa que pueda recibir información desde la línea de comando. Cuando el usuario ingresa texto, el programa lo almacenará adicionalmente en el texto de la memoria; cuando el usuario ingresa ":list", el programa genera el contenido del texto de la memoria en la línea de comando; cuando el usuario ingresa ":undo", el El programa deshará el último texto ingresado y es para borrar el texto ingresado la última vez del texto de la memoria.

Di un pequeño ejemplo para explicar este requisito, de la siguiente manera:

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

¿Cómo programarlo? Puede abrir el IDE e intentar escribirlo usted mismo primero, y luego leer mi explicación a continuación. En general, este pequeño programa no es complicado de implementar. Escribí una idea de implementación, de la siguiente manera:

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);
      }
    }
  }
}

De hecho, la implementación del modo memo es muy flexible y no existe un método de implementación fijo.Bajo diferentes requisitos comerciales y diferentes lenguajes de programación, la implementación del código puede ser diferente. El código anterior básicamente ha realizado la función de memo más básica. Sin embargo, si profundizamos, aún quedan algunos problemas por resolver, que es el segundo punto mencionado en la definición anterior: la copia de seguridad y restauración de objetos debe realizarse sin violar el principio de encapsulación. El código anterior no satisface este punto, que se refleja principalmente en los siguientes dos aspectos:

  • Primero, para restaurar el objeto InputText con una instantánea, definimos la función setText() en la clase InputText, pero esta función puede ser utilizada por otras empresas, por lo que exponer funciones que no deberían exponerse viola el principio de encapsulación;
  • En segundo lugar, la instantánea en sí es inmutable. En teoría, no debería contener ningún set() ni otras funciones que modifiquen el estado interno. Sin embargo, en la implementación del código anterior, el modelo de negocio "instantánea" reutiliza la definición de la clase InputText. La propia clase InputText tiene una serie de funciones para modificar el estado interno, por lo que usar la clase InputText para representar instantáneas viola el principio de encapsulación.

En respuesta a los problemas anteriores, hacemos dos cambios en el código. Primero, defina una clase separada (clase Instantánea) para representar la instantánea en lugar de reutilizar la clase InputText. Esta clase solo expone el método get(), sin ningún método para modificar el estado interno como set(). En segundo lugar, en la clase InputText, cambiamos el nombre del método setText() al método restoreSnapshot(), que es más específico y solo se usa para restaurar el objeto.

De acuerdo con esta idea, refactorizamos el código. El código después de la refactorización se ve así:

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);
      }
    }
  }
}

De hecho, la implementación de código anterior es una implementación de código típica del patrón de memorándum, y también es el método de implementación dado en muchos libros (incluidos los "Patrones de diseño" de GoF).

Además del modo memo, existe un concepto similar a este, "copia de seguridad", que se escucha con mayor frecuencia en nuestro desarrollo habitual. ¿Cuál es la diferencia y la conexión entre el modo memo y la "copia de seguridad"? De hecho, los escenarios de aplicación de los dos son muy similares y ambos se utilizan en escenarios como prevención de pérdidas, recuperación y revocación. La diferencia entre ellos es que el modo memo está más enfocado en el diseño e implementación del código, y el respaldo está más enfocado en el diseño de la arquitectura o el diseño del producto. Esto no es difícil de entender, así que no diré mucho aquí.

¿Cómo optimizar el consumo de memoria y tiempo?

En el pasado, solo presentamos brevemente el principio y la implementación clásica del modo memo, y ahora continuaremos profundizando. Si los datos del objeto a respaldar son relativamente grandes y la frecuencia de respaldo es relativamente alta, la memoria ocupada por la instantánea será relativamente grande y el tiempo de respaldo y recuperación será relativamente largo. ¿Cómo resolver este problema?

Diferentes escenarios de aplicación tienen diferentes soluciones. Por ejemplo, en el ejemplo que dimos anteriormente, el escenario de la aplicación es usar el memo para implementar la operación de deshacer, y solo admite deshacer secuencial, es decir, cada operación solo puede deshacer la última entrada y no puede omitir la entrada anterior a la última. entrada deshacer. En un escenario de aplicación de tales características, para ahorrar memoria, no necesitamos almacenar el texto completo en la instantánea, solo necesitamos registrar una pequeña información, como la longitud del texto cuando se toma la instantánea, y utilice este valor combinado con el texto almacenado en el objeto de la clase InputText para realizar la operación de deshacer.

Tomemos otro ejemplo. Supongamos que cada vez que hay un cambio de datos, necesitamos generar una copia de seguridad para su posterior restauración. Si los datos a respaldar son grandes, tal respaldo de alta frecuencia puede ser inaceptable, ya sea por el consumo de almacenamiento (memoria o disco duro) o por el consumo de tiempo. Para resolver este problema, generalmente usamos una combinación de "copia de seguridad completa de baja frecuencia" y "copia de seguridad incremental de alta frecuencia".

No hace falta decir que la copia de seguridad completa es similar a nuestro ejemplo anterior, que consiste en "tomar una instantánea" de todos los datos y guardarla. La llamada "copia de seguridad incremental" se refiere a registrar cada operación o cambio de datos.

Cuando necesitamos restaurar una copia de seguridad en un momento determinado, si hay una copia de seguridad completa en ese momento, podemos restaurarla directamente. Si no hay una copia de seguridad completa correspondiente en este momento, primero buscaremos la última copia de seguridad completa, luego la usaremos para restaurar y luego realizaremos todas las copias de seguridad incrementales entre esta copia de seguridad completa y este momento, es decir, las operaciones correspondientes. o cambios de datos. De esta forma, se puede reducir el número y la frecuencia de las copias de seguridad completas y se puede reducir el consumo de tiempo y memoria.

revisión clave

Bueno, eso es todo por el contenido de hoy. Resumamos y revisemos juntos, en qué necesitas concentrarte.

El modo Memento también se denomina modo de instantánea. Específicamente, captura el estado interno de un objeto sin violar el principio de encapsulación y guarda este estado fuera del objeto, para que el objeto pueda restaurarse a su estado anterior más adelante. La definición de este patrón expresa dos partes: una parte es almacenar copias para su posterior recuperación; la otra parte es realizar copias de seguridad y recuperación de objetos sin violar el principio de encapsulación.

Los escenarios de aplicación del modo memo también son relativamente claros y limitados, principalmente para prevención de pérdidas, revocación, recuperación, etc. Es muy similar a lo que solemos llamar "copia de seguridad". La principal diferencia entre los dos es que el patrón memo se centra más en el diseño y la implementación del código, y el respaldo se centra más en el diseño de la arquitectura o el diseño del producto.

Para la copia de seguridad de objetos grandes, el espacio de almacenamiento ocupado por la copia de seguridad será relativamente grande y el tiempo de copia de seguridad y restauración será relativamente largo. En respuesta a este problema, diferentes escenarios comerciales tienen diferentes métodos de procesamiento. Por ejemplo, solo haga una copia de seguridad de la información de recuperación necesaria, combinada con los datos más recientes para la recuperación; otro ejemplo, combine la copia de seguridad completa y la copia de seguridad incremental, la copia de seguridad completa de baja frecuencia, la copia de seguridad incremental de alta frecuencia y combine las dos para la recuperación.

discusión en clase

Hoy mencionamos que la copia de seguridad es más común en la arquitectura o el diseño de productos. Por ejemplo, puede optar por restaurar la página abierta anteriormente después de reiniciar Chrome. ¿Puede pensar en otros escenarios de aplicaciones similares?

Supongo que te gusta

Origin blog.csdn.net/qq_32907491/article/details/131275447
Recomendado
Clasificación