patron-memento

Patrón Memento: guarda y restaura el estado

  • 5 min

El Memento es una instantánea del estado interno de un objeto que puede restaurarse después sin exponer los detalles que el propio objeto encapsula.

En software, a menudo necesitamos recuperar un stafo anterior,

  1. Editores de texto: El famoso Ctrl+Z (Deshacer).
  2. Editores gráficos: recuperar la posición o propiedades anteriores de un elemento.
  3. Videojuegos: guardar una instantánea del estado relevante de la partida.

El problema es: ¿Cómo guardamos el estado de un objeto si sus variables son privadas? Si hacemos públicas todas las variables para poder guardarlas en un archivo, rompemos el Encapsulamiento. Cualquiera podría modificar la salud de nuestro personaje desde fuera.

El patrón Memento soluciona esto permitiendo guardar una “instantánea” (snapshot) del estado de un objeto, de forma que solo el propio objeto pueda leerla después.

Los 3 Actores

El patrón define tres roles muy claros para gestionar esta “caja de seguridad”:

Originator (El Creador): Es el objeto cuyo estado queremos guardar (el Editor, el Personaje). Sabe cómo crear un Memento con sus datos y cómo restaurarse a sí mismo usando uno.

Memento (El Recuerdo): Es una caja cerrada que contiene el estado. Es inmutable. Lo importante es que nadie (salvo el Originator) debe poder ver o modificar lo que hay dentro.

Caretaker (El Cuidador): Es quien guarda los Mementos (ej: el Historial). Sabe cuándo guardar y cuándo restaurar, pero no sabe qué hay dentro del Memento ni puede tocarlo.

:::

Implementación en C#

Vamos a implementar el sistema de deshacer de un pequeño Editor de Texto.

El Memento (La Caja Negra)

Esta clase almacena el estado. Fíjate que es sencilla e inmutable (solo tiene get).

public class EditorMemento
{
    // El estado guardado: Contenido del texto, posición del cursor, etc.
    public string Contenido { get; }
    public DateTime FechaGuardado { get; }

    public EditorMemento(string contenido)
    {
        Contenido = contenido;
        FechaGuardado = DateTime.Now;
    }
}
Copied!

El Originator (El Editor)

Esta es la clase principal. Tiene métodos para crear la instantánea (Guardar) y para restaurarla (Restaurar).

public class EditorTexto
{
    // Estado interno (que queremos proteger)
    private string _contenido;

    public void Escribir(string texto)
    {
        _contenido += texto;
        Console.WriteLine($"📝 Editor actual: \"{_contenido}\"");
    }

    // --- MÉTODOS DEL PATRÓN MEMENTO ---

    // Crea la "foto" del estado actual
    public EditorMemento Guardar()
    {
        Console.WriteLine("💾 Guardando estado...");
        return new EditorMemento(_contenido);
    }

    // Restaura el estado desde una "foto"
    public void Restaurar(EditorMemento memento)
    {
        _contenido = memento.Contenido;
        Console.WriteLine($"⏪ Restaurado a: \"{_contenido}\" (Fecha: {memento.FechaGuardado})");
    }
}
Copied!

El Caretaker (El Historial)

Gestiona la pila de recuerdos. Es el encargado de hacer el “Ctrl+Z”.

public class Historial
{
    // Una Pila (Stack) es ideal para deshacer (LIFO: Last In, First Out)
    private Stack<EditorMemento> _historial = new Stack<EditorMemento>();
    private EditorTexto _editor;

    public Historial(EditorTexto editor)
    {
        _editor = editor;
    }

    // Hace una copia de seguridad
    public void Backup()
    {
        _historial.Push(_editor.Guardar());
    }

    // Deshace el último cambio
    public void Deshacer()
    {
        if (_historial.Count == 0)
        {
            Console.WriteLine("⚠️ No hay nada que deshacer.");
            return;
        }

        var memento = _historial.Pop();
        _editor.Restaurar(memento);
    }
}
Copied!

Puesta en marcha

class Program
{
    static void Main(string[] args)
    {
        var editor = new EditorTexto();
        var historial = new Historial(editor);

        // 1. Escribimos algo
        editor.Escribir("Hola, ");
        historial.Backup(); // Guardamos estado 1

        // 2. Escribimos más
        editor.Escribir("Mundo ");
        historial.Backup(); // Guardamos estado 2

        // 3. Escribimos un error
        editor.Escribir("Cruelllll");
        
        // 4. ¡Ups! Deshacer (Volvemos a "Hola Mundo ")
        historial.Deshacer();

        // 5. Deshacer otra vez (Volvemos a "Hola, ")
        historial.Deshacer();
        
        // 6. Escribimos lo correcto
        editor.Escribir("amigos!");
    }
}
Copied!

Salida:

📝 Editor actual: "Hola, "
💾 Guardando estado...
📝 Editor actual: "Hola, Mundo "
💾 Guardando estado...
📝 Editor actual: "Hola, Mundo Cruelllll"
⏪ Restaurado a: "Hola, Mundo " (Fecha: ...)
⏪ Restaurado a: "Hola, " (Fecha: ...)
📝 Editor actual: "Hola, amigos!"
Copied!

Memento vs Command

Ambos patrones sirven para implementar “Deshacer”, pero funcionan de forma opuesta:

  • Command: Guarda la operación inversa. Si hiciste sumar(5), el comando guarda restar(5).

  • Ventaja: Ocupa poca memoria.

  • Desventaja: Difícil de implementar si la operación no es reversible matemáticamente o depende de estados complejos.

  • Memento: Guarda una copia del estado. Es fuerza bruta. Restaura la memoria tal cual estaba.

  • Ventaja: Restaura cualquier estado, por complejo que sea, sin saber matemáticas inversas.

  • Desventaja: Consume mucha memoria RAM. Si guardas el estado de un objeto gigante cada vez que pulsas una tecla, te quedarás sin RAM.

¿Cuándo usar Memento?

  1. Snapshots: Cuando necesitas guardar instantáneas del estado de un objeto (Videojuegos, Editores).
  2. Encapsulamiento estricto: Cuando el acceso directo a los campos del objeto para guardarlos violaría el encapsulamiento.
  3. Transacciones: Cuando necesitas volver a un estado inicial si una operación compleja falla.

Memento permite restaurar el pasado sin exponer las tripas del objeto. Conviene controlar el consumo de memoria: guardar muchas instantáneas grandes puede resultar costoso.