Design Patterns - Memento

Propósito

El patrón Memento captura y almacena el estado actual de un objeto, para poder restaurarlo a ese estado, sin exponer los detalles internos de su implementación (encapsulamiento).

Hace un snapshot del objeto sin exponer su estado interno.

Problema

Ataca varios problemas:

Restauración de Estado: Permite guardar y restaurar el estado de un objeto a un estado anterior sin exponer su implementación interna.

Violación del Encapsulamiento: Evita que el estado interno del objeto sea expuesto o modificado directamente, manteniendo el encapsulamiento.

Solución

Permite que un objeto (el “originador”) guarde su estado interno en un objeto memento y lo recupere más tarde.

Estructura

memento

Participantes

  • Memento: Objeto que almacena el estado del Originador.
  • Originator: Crea un Memento con su estado actual y lo usa para restaurar su estado.
  • Caretaker: El objeto que mantiene el historial de Mementos, pero no opera ni examina los contenidos del Memento.

Cuándo Usarlo

Este patrón es recomendable cuando:

  • se necesita la funcionalidad de restaurar el estado de un objeto a un estado anterior.
  • se quiere mantener un historial de estados del objeto sin exponer su implementación.

Ventajas

Cumplimiento del Encapsulamiento: Mantiene el encapsulamiento completo del objeto de origen.

Simplicidad para el Origen: Simplifica el objeto de origen, ya que delega el manejo de estados a otro objeto.

Historial de Estados: Permite mantener un historial de estados del objeto.

Desventajas

Uso de Memoria: Almacenar mementos puede requerir una cantidad importante de memoria, sobre todo si los estados son grandes o se guardan con frecuencia.

Complejidad: Se necesitan clases adicionales para los mementos y su manejo.

Potencial de Objetos Obsoletos: Si el objeto de origen cambia significativamente, los mementos antiguos pueden volverse incompatibles o irrelevantes.

Ejemplo: Editor de Texto con Funcionalidad Deshacer

Debemos crear un editor de texto que permite a los usuarios crear y editar documentos.

Una característica crucial es la capacidad de deshacer y rehacer sus cambios. Esta funcionalidad mejora el uso y brinda seguridad, permitiendo hacer cambios sin temor a cometer errores permanentes.

Problema

El desafío principal es implementar la funcionalidad de “deshacer” de manera eficiente y sin exponer o comprometer la estructura interna del objeto que representa el documento de texto.

Los usuarios deben poder deshacer una serie de cambios en el texto a un estado anterior específico, sin afectar la integridad del editor.

La implementación directa de esta funcionalidad puede llevar a un diseño complicado y propenso a errores.

Solución planteada

Elegimos el patrón Memento, porque nos permite guardar y restaurar el estado anterior de un objeto (en este caso, el contenido del Editor de Texto) sin revelar los detalles de su implementación.

El patrón Memento brinda una forma de capturar y almacenar el estado interno de un objeto a través de snapshots en un momento determinado, y luego restaurar el objeto a esos estados anteriores, todo ello manteniendo la encapsulación.

Creamos el TextMemento, que va a mantener el estado. La clase TextEditor, el originador, vamos a querer mantener el estado de este objeto. El Caretaker, que va a mantener una pila de Memento, para poder deshacer los estados. Finalmente el cliente, que configura y usa el Memento.

memento

Código Java

Codificamos en Java lo que preparamos en el diagrama.

Creamos la clase Memento:


              
  class TextMemento {
      private final String state;
  
      public TextMemento(String state) {
          this.state = state;
      }
  
      public String getState() {
          return state;
      }
  }
              
              

Implementamos el Originator:


              
  class TextEditor {
      private String content;
  
      public void setText(String text) {
          content = text;
      }
  
      public String getText() {
          return content;
      }
  
      public TextMemento save() {
          return new TextMemento(content);
      }
  
      public void restore(TextMemento memento) {
          content = memento.getState();
      }
  }
              
              

Definimos la interfaz CareTaker:


              
  class Caretaker {
      private final Stack mementos = new Stack<>();
  
      public void saveMemento(TextMemento memento) {
          mementos.push(memento);
      }
  
      public TextMemento getMemento() {
          if (!mementos.empty()) {
              return mementos.pop();
          }
          return null;
      }
  }
              
              

El código cliente:


              
  public class EditorApp {
      public static void main(String[] args) {
          TextEditor editor = new TextEditor();
          Caretaker caretaker = new Caretaker();
  
          editor.setText(“Hello”);
          caretaker.saveMemento(editor.save());
  
          editor.setText(“Hello, World!”);
          caretaker.saveMemento(editor.save());
  
          // Deshacer al estado anterior
          editor.restore(caretaker.getMemento());
          System.out.println(editor.getText());  // Debería imprimir “Hello”
  
          // Deshacer otro estado
          editor.restore(caretaker.getMemento());
          System.out.println(editor.getText());  // Debería imprimir el texto inicial o estar vacío
      }
  }
              
              

Mapeo (del ejemplo a Participantes)

Los participantes que vimos antes son: Memento, Originator, CareTaker, Client:

  • TextMemento (Memento): Almacena el estado del TextEditor.
  • TextEditor (Originator): Crea y usa mementos para guardar y restaurar su estado.
  • Caretaker(Caretaker): Realiza un seguimiento y gestiona los mementos sin modificarlos.
  • EditorApp (Client): Usa el Originator y el Caretaker para realizar operaciones.

Conclusiones

En este ejemplo vimos cómo EditorApp (Cliente) interactúa con TextEditor (Originator) y Caretaker para guardar y restaurar estados del texto usando TextMemento (Memento).

Nos permitió tomar snpashots del estado del objeto y restaurarlo, de manera sencilla.

Patrones relacionados

  • Command

Se puede usar con Memento para implementar acciones reversibles, como “deshacer“.

  • Iterator

Se puede usar para recorrer una colección de Mementos.