Patrón de diseño de recuerdo en Java

1. Información general

En este tutorial, aprenderemos qué es el patrón de diseño Memento y cómo usarlo.

Primero, repasaremos un poco de teoría. Luego, crearemos un ejemplo donde ilustraremos el uso del patrón.

2. ¿Qué es el patrón de diseño Memento?

El Memento Design Pattern, descrito por Gang of Four en su libro, es un patrón de diseño conductual. El patrón de diseño Memento ofrece una solución para implementar acciones que se pueden deshacer. Podemos hacer esto guardando el estado de un objeto en un instante dado y restaurándolo si las acciones realizadas desde entonces deben deshacerse.

Prácticamente, el objeto cuyo estado debe guardarse se denomina Originador. El cuidador es el objeto que desencadena el guardado y la restauración del estado, que se denomina Memento.

El objeto Memento debe exponer la menor información posible al cuidador. Esto es para asegurar que no expongamos el estado interno del Originador al mundo exterior, ya que rompería los principios de encapsulación. Sin embargo, el originador debe acceder a suficiente información para restaurar el estado original.

Veamos un diagrama de clases rápido que ilustra cómo los diferentes objetos interactúan entre sí:

Como podemos ver, el Originador puede producir y consumir un Memento. Mientras tanto, el cuidador solo conserva el estado antes de restaurarlo. La representación interna del Originador se mantiene oculta al mundo externo.

Aquí, usamos un solo campo para representar el estado del Originador, aunque no estamos limitados a un campo y podríamos haber usado tantos campos como fuera necesario . Además, el estado contenido en el objeto Memento no tiene que coincidir con el estado completo del Originador. Siempre que la información guardada sea suficiente para restaurar el estado del Originador, estamos listos para comenzar.

3. ¿Cuándo utilizar el patrón de diseño Memento?

Por lo general, el patrón de diseño de Memento se utilizará en situaciones en las que algunas acciones no se pueden deshacer, por lo que es necesario retroceder a un estado anterior. Sin embargo, si el estado del originador es pesado, el uso del patrón de diseño de Memento puede generar un proceso de creación costoso y un mayor uso de la memoria.

4. Ejemplo del patrón Memento

4.1. Muestra inicial

Veamos ahora un ejemplo del patrón de diseño Memento. Imaginemos que tenemos un editor de texto:

public class TextEditor { private TextWindow textWindow; public TextEditor(TextWindow textWindow) { this.textWindow = textWindow; } }

Tiene una ventana de texto, que contiene el texto ingresado actualmente y proporciona una forma de agregar más texto:

public class TextWindow { private StringBuilder currentText; public TextWindow() { this.currentText = new StringBuilder(); } public void addText(String text) { currentText.append(text); } }

4.2. Recuerdo

Ahora, imaginemos que queremos que nuestro editor de texto implemente algunas funciones para guardar y deshacer. Al guardar, queremos que se guarde nuestro texto actual. Por lo tanto, al deshacer los cambios posteriores, restauraremos nuestro texto guardado.

Para hacer eso, usaremos el patrón de diseño Memento. Primero, crearemos un objeto que contenga el texto actual de la ventana:

public class TextWindowState { private String text; public TextWindowState(String text) { this.text = text; } public String getText() { return text; } }

Este objeto es nuestro Memento. Como podemos ver, elegimos usar String en lugar de StringBuilder para evitar cualquier actualización del texto actual por parte de personas externas.

4.3. Autor

Después de eso, tendremos que proporcionar a la clase TextWindow métodos para crear y consumir el objeto Memento, haciendo de TextWindow nuestro Originador:

public TextWindowState save() { return new TextWindowState(wholeText.toString()); } public void restore(TextWindowState save) { currentText = new StringBuilder(save.getText()); }

El método save () nos permite crear el objeto, mientras que el método restore () lo consume para restaurar el estado anterior.

4.4. Vigilante

Finalmente, tenemos que actualizar nuestra clase TextEditor . Como cuidador, mantendrá el estado del originador y pedirá restaurarlo cuando sea necesario:

private TextWindowState savedTextWindow; public void hitSave() { savedTextWindow = textWindow.save(); } public void hitUndo() { textWindow.restore(savedTextWindow); }

4.5. Prueba de la solución

Veamos si funciona mediante una ejecución de muestra. Imagine que agregamos texto a nuestro editor, lo guardamos, luego agregamos algo más y, finalmente, deshacemos. Para lograr eso, agregaremos un método print () en nuestro TextEditor que devuelve una cadena del texto actual:

TextEditor textEditor = new TextEditor(new TextWindow()); textEditor.write("The Memento Design Pattern\n"); textEditor.write("How to implement it in Java?\n"); textEditor.hitSave(); textEditor.write("Buy milk and eggs before coming home\n"); textEditor.hitUndo(); assertThat(textEditor.print()).isEqualTo("The Memento Design Pattern\nHow to implement it in Java?\n");

Como podemos ver, la última oración no es parte del texto actual, ya que el Memento se guardó antes de agregarlo.

5. Conclusión

En este breve artículo, explicamos el patrón de diseño de Memento y para qué se puede utilizar. También revisamos un ejemplo que ilustra su uso en un editor de texto simple.

El código completo utilizado en este artículo se puede encontrar en GitHub.