Patrón Memento
Captura y restaura el estado interno de un objeto sin violar el encapsulamiento. Un patrón de comportamiento para deshacer/rehacer.
Visión General
El Patrón Memento es un patrón de diseño de comportamiento que te permite guardar y restaurar el estado anterior de un objeto sin revelar su estructura interna. Es la base de la funcionalidad de deshacer/rehacer en aplicaciones como editores de texto, programas de dibujo, y gestión de estado de juegos.
Cuándo Usarlo
Usa el Patrón Memento cuando:
- Necesitas implementar funcionalidad de deshacer y rehacer
- Quieres guardar puntos de control del estado de un objeto
- Debes preservar el encapsulamiento y no exponer el estado interno directamente
- La restauración de estado debería ser posible sin que el cliente conozca los internals del objeto
Solución
Python
class EditorMemento:
def __init__(self, content: str, cursor: int):
self._content = content
self._cursor = cursor
@property
def content(self) -> str:
return self._content
@property
def cursor(self) -> int:
return self._cursor
class TextEditor:
def __init__(self):
self._content = ""
self._cursor = 0
def type(self, text: str):
self._content = self._content[:self._cursor] + text
self._cursor += len(text)
def save(self) -> EditorMemento:
return EditorMemento(self._content, self._cursor)
def restore(self, memento: EditorMemento):
self._content = memento.content
self._cursor = memento.cursor
@property
def content(self) -> str:
return self._content
# Uso con historial
class History:
def __init__(self):
self._history = []
def push(self, memento):
self._history.append(memento)
def pop(self):
if not self._history:
return None
return self._history.pop()
editor = TextEditor()
history = History()
history.push(editor.save())
editor.type("Hola ")
history.push(editor.save())
editor.type("Mundo!")
print(editor.content) # Hola Mundo!
editor.restore(history.pop())
print(editor.content) # Hola
editor.restore(history.pop())
print(editor.content) # (vacío)
JavaScript
class EditorMemento {
constructor(content, cursor) {
this.content = content;
this.cursor = cursor;
}
}
class TextEditor {
constructor() {
this._content = "";
this._cursor = 0;
}
type(text) {
this._content = this._content.slice(0, this._cursor) + text;
this._cursor += text.length;
}
save() {
return new EditorMemento(this._content, this._cursor);
}
restore(memento) {
this._content = memento.content;
this._cursor = memento.cursor;
}
get content() {
return this._content;
}
}
// Uso
class History {
constructor() {
this.history = [];
}
push(memento) {
this.history.push(memento);
}
pop() {
return this.history.pop();
}
}
const editor = new TextEditor();
const history = new History();
history.push(editor.save());
editor.type("Hola ");
history.push(editor.save());
editor.type("Mundo!");
console.log(editor.content); // Hola Mundo!
editor.restore(history.pop());
console.log(editor.content); // Hola
Java
public class EditorMemento {
private final String content;
private final int cursor;
public EditorMemento(String content, int cursor) {
this.content = content;
this.cursor = cursor;
}
public String getContent() { return content; }
public int getCursor() { return cursor; }
}
public class TextEditor {
private String content = "";
private int cursor = 0;
public void type(String text) {
content = content.substring(0, cursor) + text;
cursor += text.length();
}
public EditorMemento save() {
return new EditorMemento(content, cursor);
}
public void restore(EditorMemento memento) {
this.content = memento.getContent();
this.cursor = memento.getCursor();
}
public String getContent() { return content; }
}
// Uso con historial
import java.util.ArrayDeque;
import java.util.Deque;
public class History {
private final Deque<EditorMemento> stack = new ArrayDeque<>();
public void push(EditorMemento memento) {
stack.push(memento);
}
public EditorMemento pop() {
return stack.isEmpty() ? null : stack.pop();
}
}
// Demo
TextEditor editor = new TextEditor();
History history = new History();
history.push(editor.save());
editor.type("Hola ");
history.push(editor.save());
editor.type("Mundo!");
System.out.println(editor.getContent());
editor.restore(history.pop());
System.out.println(editor.getContent());
Explicación
El Patrón Memento tiene tres roles:
- Originador (
TextEditor): El objeto cuyo estado necesita ser guardado - Memento (
EditorMemento): Una instantánea inmutable del estado del originador - Cuidador (
History): Gestiona los mementos (cuándo guardar, cuándo restaurar) sin acceder a su contenido
El beneficio clave es que el estado interno del memento es opaco al cuidador, preservando el encapsulamiento.
Variantes
| Variante | Descripción | Caso de Uso |
|---|---|---|
| Instantánea Completa | Almacena todo el estado del objeto | Objetos pequeños, instantáneas infrecuentes |
| Delta/Incremental | Almacena solo campos cambiados | Objetos grandes, instantáneas frecuentes |
| Memento Serializable | Usa serialización para copia profunda | Grafos de objetos complejos |
| Command + Memento | Comandos almacenan mementos para deshacer | Sistemas transaccionales, editores |
Lo que Funciona
- Mantén los mementos inmutables después de la creación para prevenir manipulación accidental
- Limita la vida útil de los mementos — historiales grandes consumen memoria sustancial
- Considera serialización para grafos de objetos complejos, pero sé consciente de los costos de rendimiento
- Implementa una interfaz de memento que solo expone métodos de restauración de estado al originador
- Usa mementos delta para objetos grandes donde solo cambian algunos campos
Errores Comunes
- Exponer el estado interno del memento al cuidador, rompiendo el encapsulamiento
- Almacenar demasiadas instantáneas completas, causando uso excesivo de memoria
- No manejar versionado de mementos cuando la estructura del originador cambia con el tiempo
- Olvidar validar mementos antes de la restauración (instantáneas corruptas o incompatibles)
- Permitir que los originadores modifiquen mementos después de la creación, causando comportamiento impredecible de deshacer
Lectura Adicional
- Documentación oficial: consulta la referencia actualizada del framework o herramienta utilizada.
- Guías relacionadas: explora las guías de memento y pattern para profundizar.
- Patrones complementarios: revisa los patrones de diseño aplicables a tu stack tecnológico.
- Postmortems públicos: estudia incidentes reales de equipos que enfrentaron problemas similares en producción.
Notas de Producción
- Despliega gradualmente usando canary o blue-green para detectar regresiones temprano.
- Configura alertas para errores, latencia p99 y tasa de fallos antes de habilitar en producción.
- Documenta el rollback en el runbook; prueba el procedimiento en staging al menos una vez por trimestre.
- Revisa logs estructurados con correlation IDs para trazar requests end-to-end en incidentes.
Puntos Clave
- Aplica patrón memento cuando necesites una solución práctica para tu caso de uso.
- Monitorea el rendimiento después de implementar; mide latencia, errores y uso de recursos antes y después.
- Revisa la sección de Troubleshooting ante errores comunes; la mayoría tienen causa raíz documentada con solución.
- Mantén dependencias actualizadas y ejecuta tests en CI para prevenir regresiones en producción.
Troubleshooting
- Pattern does not fit the problem: re-evaluate the forces (performance, scalability, team size, coupling). A pattern is only appropriate when its trade-offs match your constraints.
- Too many abstractions: if adding a pattern increases complexity without a clear benefit, simplify. Not every module needs a factory, decorator, or strategy.
- Tight coupling after refactoring: check that interfaces are stable and dependencies point inward.
- Tests break when the design changes: favor stable contracts over internal structure.
- Performance regression from indirection: measure before and after. Layers, decorators, and adapters can add latency; cache or inline hot paths if needed.
Errores Comunes en Producción
- Aplicar el patrón donde no se necesita abstracción, agregando complejidad accidental.
- Dejar que el patrón se filtre en módulos no relacionados y confundir los límites de responsabilidad.
- Sobre-ingeniería en la primera implementación en lugar de comenzar simple y medir el dolor.
- Saltar los tests de contrato, de modo que las refactorizaciones rompan consumidores en silencio.
- Ignorar modos de fallo que el patrón no cubre.
- Usar el patrón como opción por defecto en lugar de elegir la herramienta adecuada para la escala actual.
- Olvidar documentar cuándo dejar de usar el patrón y qué lo reemplaza.
- Carecer de observabilidad sobre rendimiento y propagación de errores del patrón.
Preguntas frecuentes
¿Es este patrón adecuado para proyectos pequeños?
Para proyectos pequeños con pocos componentes, este patrón puede añadir complejidad innecesaria. Empieza simple e introduce el patrón cuando sientas el problema que resuelve.
¿Cómo se compara este patrón con alternativas?
Cada patrón hace diferentes trade-offs. Revisa la tabla de variantes arriba y considera tus restricciones específicas: tamaño del equipo, requisitos de rendimiento y planes de escalado.
¿Puedo aplicar este patrón parcialmente?
Sí. Muchos equipos adoptan patrones incrementalmente. Empieza con la idea central y añade sofisticación según sea necesario. El patrón es una guía, no un blueprint estricto.
Recursos Relacionados
Patrón Command
Encapsula una petición como un objeto, permitiendo parametrizar clientes con colas, logs y operaciones deshacibles. Patrón de diseño conductual.
PatternPatrón State
Permite que un objeto altere su comportamiento cuando cambia su estado interno. Un patrón de comportamiento para máquinas de estados finitos.
PatternPatrón Prototype
Crea nuevos objetos copiando los existentes. Un patrón de diseño creacional para clonación y duplicación de objetos.
PatternPatrón Chain of Responsibility
Pasa solicitudes a lo largo de una cadena de manejadores hasta que uno la procese. Un patrón de comportamiento para desacoplar emisores y receptores.
PatternPatrón Interpreter
Define una representación para la gramática de un lenguaje junto con un intérprete que usa la representación para interpretar oraciones. Un patrón de diseño de comportamiento para mini-lenguajes.