Python Patrón Memento (Memento)

El patrón Memento es un patrón de diseño de comportamiento que permite capturar y guardar el estado interno de un objeto sin romper la encapsulación, para poder restaurarlo a un estado anterior cuando sea necesario.

Imagina que estás jugando a un juego que ofrece la función de "guardar partida". Puedes guardar el progreso actual en cualquier momento; si luego pierdes o quieres reintentar un nivel, puedes cargar la partida guardada anteriormente. El patrón Memento es la solución de programación para implementar esta función de "guardar-cargar".


¿Por qué necesitamos el patrón Memento?

Escenarios de aplicación

  • Editor de texto: implementar las funciones de deshacer (Undo) y rehacer (Redo)
  • Desarrollo de juegos: guardar el progreso y el estado del juego
  • Software gráfico: soportar el historial de operaciones
  • Procesamiento de transacciones: revertir a un estado anterior cuando una operación falla

Problemas que resuelve

  • Necesita guardar el estado de un objeto sin exponer los detalles internos del objeto
  • Proporciona un mecanismo de restauración de estado, compatible con la función de "arrepentimiento"
  • Mantiene la encapsulación del objeto, evitando mezclar la lógica de guardado de estado con la lógica de negocio

Componentes principales del patrón Memento

El patrón Memento incluye tres roles principales:

1. Originador (Originator)

  • El objeto que necesita guardar el estado
  • Crea un memento para registrar el estado actual
  • Usa el memento para restaurar el estado anterior

2. Memento (Memento)

  • Almacena el estado interno del objeto originador
  • Impide que objetos distintos del originador accedan al contenido del memento

3. Administrador (Caretaker)

  • Se encarga de guardar los mementos
  • No puede operar ni inspeccionar el contenido del memento


Implementación del patrón Memento en Python

Implementemos el patrón Memento mediante un ejemplo de editor de texto.

Implementación básica

Ejemplo

class TextEditorMemento:
    """Clase Memento - guarda el estado del editor de texto"""
   
    def __init__(self, content):
        self._content = content
   
    def get_content(self):
        """Obtener el contenido guardado"""
        return self._content


class TextEditor:
    """Clase Originator - editor de texto"""
   
    def __init__(self):
        self._content = ""
   
    def write(self, text):
        """Escribir texto"""
        self._content += text
   
    def get_content(self):
        """Obtener el contenido actual"""
        return self._content
   
    def save(self):
        """Crear memento - guardar el estado actual"""
        return TextEditorMemento(self._content)
   
    def restore(self, memento):
        """Restaurar estado - leer desde el memento"""
        self._content = memento.get_content()


class HistoryManager:
    """Clase Administrador - administrador de historial"""
   
    def __init__(self):
        self._mementos = []
   
    def push(self, memento):
        """Guardar memento"""
        self._mementos.append(memento)
   
    def pop(self):
        """Obtener el último memento"""
        if self._mementos:
            return self._mementos.pop()
        return None

Código de ejemplo completo

Ejemplo

# Ejemplo completo del patrón Memento
def memo_pattern_demo():
    # Crear el editor de texto y el administrador de historial
    editor = TextEditor()
    history = HistoryManager()
   
    print("=== Demostración del patrón Memento del editor de texto ==="\n")
   
    # Primera edición y guardar
    editor.write("Hello, ")
    history.push(editor.save())
    print(f"Contenido actual: {editor.get_content()}")
    print("✅ Estado guardado\n")
   
    # Segunda edición y guardar
    editor.write("World!")
    history.push(editor.save())
    print(f"Contenido actual: {editor.get_content()}")
    print("✅ Estado guardado\n")
   
    # Tercera edición sin guardar
    editor.write(" This is new text.")
    print(f"Contenido actual: {editor.get_content()}")
    print("❌ Edición actual no guardada\n")
   
    # Operación de deshacer - restaurar al estado guardado anterior
    memento = history.pop()
    if memento:
        editor.restore(memento)
        print(f"Contenido después de deshacer: {editor.get_content()}")
        print("↩️ Deshecho al punto de guardado anterior\n")
   
    # Deshacer de nuevo
    memento = history.pop()
    if memento:
        editor.restore(memento)
        print(f"Contenido después de deshacer de nuevo: {editor.get_content()}")
        print("↩️ Deshecho al estado inicial")


# Ejecutar la demo
if __name__ == "__main__":
    memo_pattern_demo()

Resultado de la ejecución:


Aplicación avanzada: Memento que admite múltiples estados

En aplicaciones reales, puede que necesitemos guardar múltiples atributos de un objeto. A continuación se muestra un ejemplo más complejo:

Ejemplo

import datetime
import json

class GameStateMemento:
    """Memento del estado del juego"""
   
    def __init__(self, level, score, player_health, inventory):
        self._state = {
            'level': level,
            'score': score,
            'player_health': player_health,
            'inventory': inventory.copy(),  # Crear una copia para evitar problemas de referencia
            'timestamp': datetime.datetime.now().isoformat()
        }
   
    def get_state(self):
        """Obtener el estado completo"""
        return self._state.copy()
   
    def get_timestamp(self):
        """Obtener el tiempo de guardado"""
        return self._state['timestamp']


class Game:
    """Clase de juego - Originador"""
   
    def __init__(self):
        self.level = 1
        self.score = 0
        self.player_health = 100
        self.inventory = ['sword', 'potion']
   
    def play(self, level_increase=1, score_increase=100, health_change=0, new_item=None):
        """Simular la partida"""
        self.level += level_increase
        self.score += score_increase
        self.player_health += health_change
       
        if new_item and new_item not in self.inventory:
            self.inventory.append(new_item)
       
        # La vida no puede ser negativa
        self.player_health = max(0, self.player_health)
   
    def display_status(self):
        """Mostrar el estado actual"""
        status = f"""
🎮 Estado del juego:
Nivel: {self.level}
Puntuación: {self.score}
Vida: {self.player_health}
Inventario: {', '.join(self.inventory)}
        """

        print(status)
   
    def save_game(self):
        """Guardar el estado del juego"""
        return GameStateMemento(
            self.level,
            self.score,
            self.player_health,
            self.inventory
        )
   
    def load_game(self, memento):
        """Cargar el estado del juego"""
        state = memento.get_state()
        self.level = state['level']
        self.score = state['score']
        self.player_health = state['player_health']
        self.inventory = state['inventory']


class SaveManager:
    """Gestor de guardados"""
   
    def __init__(self):
        self.saves = {}
   
    def create_save(self, save_name, memento):
        """Crear guardado"""
        self.saves[save_name] = memento
        print(f💾 ¡Partida guardada '{save_name}' creada con éxito!)
   
    def load_save(self, save_name):
        Cargar partida guardada
        if save_name in self.saves:
            print(f🔄 Cargando partida guardada '{save_name}'...)
            return self.saves[save_name]
        else:
            print(f❌ ¡La partida guardada '{save_name}' no existe!)
            return None
   
    def list_saves(self):
        Listar todas las partidas guardadas
        if not self.saves:
            print(📁 No hay partidas guardadas)
            return
       
        print("\n📋 Lista de partidas guardadas:)
        for name, memento in self.saves.items():
            timestamp = memento.get_timestamp()
            print(f- {name} (guardada en: {timestamp}))


# Demostración de ejemplo avanzado
def advanced_memo_demo():
    print(=== Demostración del sistema de guardado ===\n")
   
    game = Game()
    save_manager = SaveManager()
   
    # Estado inicial
    print(🎯 Iniciar nuevo juego:)
    game.display_status()
   
    # Progreso del juego
    print(🚀 Jugando...)
    game.play(level_increase=2, score_increase=500, new_item='shield')
    game.display_status()
   
    # Guardar primera partida
    save_manager.create_save(Primer nivel completado, game.save_game())
   
    # Continuar partida
    print(⚔️ Continuando aventura...)
    game.play(level_increase=1, score_increase=200, health_change=-30, new_item='magic_wand')
    game.display_status()
   
    # Guardar segunda partida
    save_manager.create_save(Inicio del segundo nivel, game.save_game())
   
    # Mostrar lista de partidas guardadas
    save_manager.list_saves()
   
    # Cargar la primera partida guardada
    print("\n⏪ Volver al estado del primer nivel superado:)
    first_save = save_manager.load_save(Primer nivel completado)
    if first_save:
        game.load_game(first_save)
        game.display_status()


# Ejecutar ejemplo avanzado
if __name__ == "__main__":
    advanced_memo_demo()

Ventajas y desventajas del patrón Memento

Ventajas

  1. Protección de encapsulación: no expone el estado interno del objeto, mantiene la encapsulación
  2. Simplica el originador: separa la lógica de guardado de estado de la lógica de negocio
  3. Fácil de implementar la restauración: proporciona un mecanismo simple de restauración de estado
  4. Soporta múltiples deshaceres: puede guardar el estado en múltiples momentos

Desventajas

  1. Consumo de memoria: si el estado es muy grande o se guarda con frecuencia, consumirá mucha memoria
  2. Sobrecarga del administrador: requiere clases adicionales para administrar los mementos
  3. Gestión del ciclo de vida: es necesario manejar adecuadamente la creación y destrucción de los mementos

Mejores prácticas y consideraciones

1. Estrategias de optimización de memoria

Ejemplo

# Limitar el número de registros históricos guardados
class LimitedHistoryManager:
    def __init__(self, max_size=10):
        self._mementos = []
        self._max_size = max_size
   
    def push(self, memento):
        if len(self._mementos) >= self._max_size:
            # Eliminar el registro más antiguo
            self._mementos.pop(0)
        self._mementos.append(memento)

2. Manejo de estados de objetos complejos

Para objetos que contienen referencias complejas, es necesario asegurarse de que el memento cree una copia profunda:

Ejemplo

import copy

class ComplexMemento:
    def __init__(self, complex_state):
        # Usar copia profunda para evitar problemas de referencia
        self._state = copy.deepcopy(complex_state)

3. Guardado selectivo

No todos los estados necesitan ser guardados; se puede optar por guardar solo las propiedades importantes:

Ejemplo

def create_selective_memento(self):
    """Guardar selectivamente el estado importante"""
    important_state = {
        'essential_data': self.essential_data,
        'critical_settings': self.critical_settings
        # Ignorar datos temporales y cachés
    }
    return SelectiveMemento(important_state)

Escenarios de aplicación real

1. Editor de gráficos

Ejemplo

class GraphicEditor:
    def save_state(self):
        return GraphicMemento(
            self.selected_shapes.copy(),
            self.canvas_state,
            self.view_settings
        )

2. Administrador de configuración

Ejemplo

class ConfigManager:
    def backup_config(self):
        return ConfigMemento(
            self.current_settings.copy(),
            self.user_preferences
        )

3. Operaciones transaccionales

Ejemplo

class DatabaseTransaction:
    def create_checkpoint(self):
        return TransactionMemento(
            self.connection_state,
            self.pending_operations.copy()
        )

Resumen

El patrón Memento es un patrón de diseño potente y práctico que nos proporciona un elegante mecanismo de guardado y restauración de estados. Al separar la lógica de guardado de estados de la lógica de negocio, no solo mantiene la limpieza del código, sino que también mejora la mantenibilidad del sistema.

Puntos clave:

  • El núcleo del patrón Memento es el mecanismo de "guardar-restaurar"
  • Los tres roles principales cumplen cada uno sus funciones con responsabilidades claras
  • En el uso práctico, hay que prestar atención a la gestión de memoria y a la selección de estados
  • Es adecuado para escenarios que requieren deshacer, rehacer o revertir estados
Otras extensiones