Imagen destacada del tutorial: Fundamentos de Programación: Patrones de Diseño de Comportamiento
Patrones de Diseño

Fundamentos de Programación: Patrones de Diseño de Comportamiento

José Elías Romero Guanipa
02 Sep 2025

Aprende patrones de diseño de comportamiento como Observer, Strategy, Command y State con ejemplos prácticos en Python.

patrones diseño patrones comportamiento observer strategy command +7 más

¡Domina los patrones de diseño de comportamiento! En este tutorial especializado te guiaré paso a paso para que aprendas los patrones fundamentales de comportamiento, incluyendo Observer, Strategy, Command, State, Template Method, Iterator, Mediator y Memento, con ejemplos prácticos y casos de uso reales en Python.

Objetivo: Aprender los patrones de diseño de comportamiento más importantes, sus implementaciones en Python, ventajas, desventajas y cuándo aplicarlos para gestionar algoritmos, estados y comunicaciones entre objetos.

Índice

Paso 1: ¿Qué son los patrones de comportamiento?

Los patrones de diseño de comportamiento se centran en la comunicación entre objetos y la asignación de responsabilidades entre ellos. Estos patrones ayudan a definir cómo los objetos interactúan y se distribuyen las responsabilidades.

¿Por qué son importantes?

  • Flexibilidad: Permiten cambiar el comportamiento en tiempo de ejecución
  • Desacoplamiento: Reducen las dependencias entre objetos
  • Reutilización: Facilitan la composición de comportamientos
  • Mantenimiento: Hacen el código más comprensible y modificable

Clasificación de patrones de comportamiento

Patrón Propósito Complejidad
Observer Notificaciones de cambios Media
Strategy Algoritmos intercambiables Baja
Command Encapsular operaciones Media
State Cambio por estado Media
Template Method Esqueleto de algoritmo Baja
Iterator Recorrer colecciones Baja
Mediator Comunicación centralizada Alta
Memento Guardar/restaurar estado Media

Paso 2: Observer - Notificaciones de cambios

El patrón Observer define una dependencia de uno a muchos entre objetos, de modo que cuando un objeto cambia su estado, todos sus dependientes son notificados y actualizados automáticamente.

Implementación básica

from abc import ABC, abstractmethod
from typing import List

class Observador(ABC):
    @abstractmethod
    def actualizar(self, sujeto):
        pass

class Sujeto:
    def __init__(self):
        self._observadores: List[Observador] = []
        self._estado = None

    def agregar_observador(self, observador: Observador):
        if observador not in self._observadores:
            self._observadores.append(observador)

    def remover_observador(self, observador: Observador):
        self._observadores.remove(observador)

    def notificar_observadores(self):
        for observador in self._observadores:
            observador.actualizar(self)

    @property
    def estado(self):
        return self._estado

    @estado.setter
    def estado(self, valor):
        self._estado = valor
        self.notificar_observadores()

# Observadores concretos
class ObservadorConcretoA(Observador):
    def actualizar(self, sujeto):
        print(f"Observador A: Estado cambiado a {sujeto.estado}")

class ObservadorConcretoB(Observador):
    def actualizar(self, sujeto):
        print(f"Observador B: Recibido cambio - {sujeto.estado}")

# Uso
sujeto = Sujeto()

observador_a = ObservadorConcretoA()
observador_b = ObservadorConcretoB()

sujeto.agregar_observador(observador_a)
sujeto.agregar_observador(observador_b)

sujeto.estado = "Nuevo estado"
sujeto.estado = "Otro estado"

sujeto.remover_observador(observador_b)
sujeto.estado = "Estado sin observador B"

Observer para sistema de notificaciones

class SistemaNotificaciones(Sujeto):
    def __init__(self):
        super().__init__()
        self.mensajes = []

    def enviar_mensaje(self, mensaje: str):
        self.mensajes.append(mensaje)
        self.estado = f"Nuevo mensaje: {mensaje}"

class ClienteEmail(Observador):
    def __init__(self, email: str):
        self.email = email

    def actualizar(self, sujeto):
        ultimo_mensaje = sujeto.mensajes[-1] if sujeto.mensajes else "Sin mensajes"
        print(f"Enviando email a {self.email}: {ultimo_mensaje}")

class ClienteSMS(Observador):
    def __init__(self, telefono: str):
        self.telefono = telefono

    def actualizar(self, sujeto):
        ultimo_mensaje = sujeto.mensajes[-1] if sujeto.mensajes else "Sin mensajes"
        print(f"Enviando SMS a {self.telefono}: {ultimo_mensaje}")

# Uso
sistema = SistemaNotificaciones()

cliente1 = ClienteEmail("[email protected]")
cliente2 = ClienteSMS("+123456789")
cliente3 = ClienteEmail("[email protected]")

sistema.agregar_observador(cliente1)
sistema.agregar_observador(cliente2)
sistema.agregar_observador(cliente3)

sistema.enviar_mensaje("Sistema iniciado")
sistema.enviar_mensaje("Backup completado")
sistema.enviar_mensaje("Error crítico detectado")

Paso 3: Strategy - Algoritmos intercambiables

El patrón Strategy define una familia de algoritmos, encapsula cada uno y los hace intercambiables. Permite que el algoritmo varíe independientemente de los clientes que lo usan.

Implementación básica

from abc import ABC, abstractmethod
from typing import List

class EstrategiaOrdenamiento(ABC):
    @abstractmethod
    def ordenar(self, datos: List) -> List:
        pass

class OrdenamientoBurbuja(EstrategiaOrdenamiento):
    def ordenar(self, datos: List) -> List:
        print("Ordenando con burbuja")
        n = len(datos)
        for i in range(n):
            for j in range(0, n - i - 1):
                if datos[j] > datos[j + 1]:
                    datos[j], datos[j + 1] = datos[j + 1], datos[j]
        return datos

class OrdenamientoRapido(EstrategiaOrdenamiento):
    def ordenar(self, datos: List) -> List:
        print("Ordenando rápido (quicksort)")
        if len(datos) <= 1:
            return datos
        pivote = datos[len(datos) // 2]
        izquierda = [x for x in datos if x < pivote]
        medio = [x for x in datos if x == pivote]
        derecha = [x for x in datos if x > pivote]
        return self.ordenar(izquierda) + medio + self.ordenar(derecha)

class OrdenamientoSeleccion(EstrategiaOrdenamiento):
    def ordenar(self, datos: List) -> List:
        print("Ordenando por selección")
        for i in range(len(datos)):
            min_idx = i
            for j in range(i + 1, len(datos)):
                if datos[j] < datos[min_idx]:
                    min_idx = j
            datos[i], datos[min_idx] = datos[min_idx], datos[i]
        return datos

class ContextoOrdenamiento:
    def __init__(self, estrategia: EstrategiaOrdenamiento = None):
        self._estrategia = estrategia

    def establecer_estrategia(self, estrategia: EstrategiaOrdenamiento):
        self._estrategia = estrategia

    def ejecutar_ordenamiento(self, datos: List) -> List:
        if not self._estrategia:
            raise ValueError("Estrategia no establecida")
        return self._estrategia.ordenar(datos.copy())

# Uso
datos = [64, 34, 25, 12, 22, 11, 90]
contexto = ContextoOrdenamiento()

print("Datos originales:", datos)

# Usar burbuja
contexto.establecer_estrategia(OrdenamientoBurbuja())
resultado = contexto.ejecutar_ordenamiento(datos)
print("Burbuja:", resultado)

# Cambiar a quicksort
contexto.establecer_estrategia(OrdenamientoRapido())
resultado = contexto.ejecutar_ordenamiento(datos)
print("Quicksort:", resultado)

# Cambiar a selección
contexto.establecer_estrategia(OrdenamientoSeleccion())
resultado = contexto.ejecutar_ordenamiento(datos)
print("Selección:", resultado)

Strategy para procesamiento de pagos

class EstrategiaPago(ABC):
    @abstractmethod
    def procesar_pago(self, monto: float) -> str:
        pass

class PagoTarjetaCredito(EstrategiaPago):
    def procesar_pago(self, monto: float) -> str:
        return f"Procesando pago de ${monto} con tarjeta de crédito"

class PagoPayPal(EstrategiaPago):
    def procesar_pago(self, monto: float) -> str:
        return f"Procesando pago de ${monto} con PayPal"

class PagoBitcoin(EstrategiaPago):
    def procesar_pago(self, monto: float) -> str:
        return f"Procesando pago de ${monto} con Bitcoin"

class CarritoCompras:
    def __init__(self):
        self.items = []
        self._estrategia_pago = None

    def agregar_item(self, item: str, precio: float):
        self.items.append((item, precio))

    def calcular_total(self) -> float:
        return sum(precio for _, precio in self.items)

    def establecer_metodo_pago(self, estrategia: EstrategiaPago):
        self._estrategia_pago = estrategia

    def realizar_pago(self) -> str:
        if not self._estrategia_pago:
            raise ValueError("Método de pago no seleccionado")

        total = self.calcular_total()
        return self._estrategia_pago.procesar_pago(total)

# Uso
carrito = CarritoCompras()
carrito.agregar_item("Laptop", 1200.00)
carrito.agregar_item("Mouse", 45.99)
carrito.agregar_item("Teclado", 89.99)

print(f"Total: ${carrito.calcular_total():.2f}")

# Procesar con diferentes métodos
carrito.establecer_metodo_pago(PagoTarjetaCredito())
print(carrito.realizar_pago())

carrito.establecer_metodo_pago(PagoPayPal())
print(carrito.realizar_pago())

carrito.establecer_metodo_pago(PagoBitcoin())
print(carrito.realizar_pago())

Paso 4: Command - Encapsular operaciones

El patrón Command encapsula una solicitud como un objeto, permitiendo parametrizar clientes con diferentes solicitudes, encolar o registrar solicitudes, y soportar operaciones que pueden ser deshechas.

Implementación básica

from abc import ABC, abstractmethod
from typing import List

class Comando(ABC):
    @abstractmethod
    def ejecutar(self):
        pass

    @abstractmethod
    def deshacer(self):
        pass

class ComandoConcreto(Comando):
    def __init__(self, receptor, accion, parametros=None):
        self.receptor = receptor
        self.accion = accion
        self.parametros = parametros or {}
        self.estado_anterior = None

    def ejecutar(self):
        # Guardar estado anterior para poder deshacer
        self.estado_anterior = self.receptor.obtener_estado()

        # Ejecutar la acción
        metodo = getattr(self.receptor, self.accion)
        if self.parametros:
            return metodo(**self.parametros)
        else:
            return metodo()

    def deshacer(self):
        if self.estado_anterior:
            self.receptor.establecer_estado(self.estado_anterior)
            return "Operación deshecha"
        return "No se puede deshacer"

class Receptor:
    def __init__(self):
        self.estado = "Estado inicial"

    def accion_especial(self, mensaje: str):
        self.estado = f"Estado después de: {mensaje}"
        return f"Acción ejecutada: {mensaje}"

    def otra_accion(self, numero: int):
        self.estado = f"Estado numérico: {numero}"
        return f"Número procesado: {numero}"

    def obtener_estado(self):
        return self.estado

    def establecer_estado(self, estado):
        self.estado = estado

class Invocador:
    def __init__(self):
        self.historial: List[Comando] = []
        self.comandos_por_hacer: List[Comando] = []

    def establecer_comando(self, comando: Comando):
        self.comandos_por_hacer.append(comando)

    def ejecutar_comandos(self):
        resultados = []
        for comando in self.comandos_por_hacer:
            resultado = comando.ejecutar()
            self.historial.append(comando)
            resultados.append(resultado)
        self.comandos_por_hacer = []
        return resultados

    def deshacer_ultimo(self):
        if self.historial:
            comando = self.historial.pop()
            return comando.deshacer()
        return "No hay comandos para deshacer"

# Uso
receptor = Receptor()
invocador = Invocador()

# Configurar comandos
comando1 = ComandoConcreto(receptor, "accion_especial", {"mensaje": "Hola Mundo"})
comando2 = ComandoConcreto(receptor, "otra_accion", {"numero": 42})

invocador.establecer_comando(comando1)
invocador.establecer_comando(comando2)

print("Estado inicial:", receptor.obtener_estado())

# Ejecutar comandos
resultados = invocador.ejecutar_comandos()
for resultado in resultados:
    print("Resultado:", resultado)

print("Estado después de ejecutar:", receptor.obtener_estado())

# Deshacer último comando
print(invocador.deshacer_ultimo())
print("Estado después de deshacer:", receptor.obtener_estado())

# Deshacer otro comando
print(invocador.deshacer_ultimo())
print("Estado final:", receptor.obtener_estado())

Command para editor de texto

class EditorTexto:
    def __init__(self):
        self.texto = ""
        self.cursor_posicion = 0

    def insertar_texto(self, texto: str, posicion: int = None):
        if posicion is not None:
            self.cursor_posicion = posicion

        # Insertar texto en la posición del cursor
        self.texto = (self.texto[:self.cursor_posicion] + 
                     texto + 
                     self.texto[self.cursor_posicion:])
        self.cursor_posicion += len(texto)
        return f"Texto insertado: '{texto}'"

    def borrar_texto(self, cantidad: int = 1):
        if self.cursor_posicion >= cantidad:
            texto_borrado = self.texto[self.cursor_posicion - cantidad:self.cursor_posicion]
            self.texto = (self.texto[:self.cursor_posicion - cantidad] + 
                         self.texto[self.cursor_posicion:])
            self.cursor_posicion -= cantidad
            return f"Texto borrado: '{texto_borrado}'"
        return "No hay texto para borrar"

    def obtener_estado(self):
        return {
            'texto': self.texto,
            'cursor': self.cursor_posicion
        }

    def establecer_estado(self, estado):
        self.texto = estado['texto']
        self.cursor_posicion = estado['cursor']

class ComandoInsertar(Comando):
    def __init__(self, editor, texto, posicion=None):
        self.editor = editor
        self.texto = texto
        self.posicion = posicion
        self.estado_anterior = None

    def ejecutar(self):
        self.estado_anterior = self.editor.obtener_estado()
        return self.editor.insertar_texto(self.texto, self.posicion)

    def deshacer(self):
        if self.estado_anterior:
            self.editor.establecer_estado(self.estado_anterior)
            return f"Deshecha inserción de '{self.texto}'"
        return "No se puede deshacer"

class ComandoBorrar(Comando):
    def __init__(self, editor, cantidad=1):
        self.editor = editor
        self.cantidad = cantidad
        self.estado_anterior = None
        self.texto_borrado = None

    def ejecutar(self):
        self.estado_anterior = self.editor.obtener_estado()
        resultado = self.editor.borrar_texto(self.cantidad)
        if "borrado" in resultado:
            self.texto_borrado = resultado.split("'")[1]
        return resultado

    def deshacer(self):
        if self.estado_anterior and self.texto_borrado:
            self.editor.establecer_estado(self.estado_anterior)
            return f"Deshecho borrado de '{self.texto_borrado}'"
        return "No se puede deshacer"

# Uso del editor
editor = EditorTexto()
invocador = Invocador()

# Comandos de inserción
invocador.establecer_comando(ComandoInsertar(editor, "Hola"))
invocador.establecer_comando(ComandoInsertar(editor, " Mundo"))
invocador.establecer_comando(ComandoInsertar(editor, "!", len(editor.texto)))

# Ejecutar todos los comandos
resultados = invocador.ejecutar_comandos()
for resultado in resultados:
    print(resultado)

print(f"Texto final: '{editor.texto}'")

# Comando de borrado
invocador.establecer_comando(ComandoBorrar(editor, 6))  # Borrar " Mundo"
resultado = invocador.ejecutar_comandos()[0]
print(resultado)
print(f"Texto después de borrar: '{editor.texto}'")

# Deshacer borrado
print(invocador.deshacer_ultimo())
print(f"Texto después de deshacer: '{editor.texto}'")

Paso 5: State - Cambio de comportamiento por estado

El patrón State permite que un objeto altere su comportamiento cuando su estado interno cambia. El objeto parecerá haber cambiado su clase.

Implementación básica

from abc import ABC, abstractmethod

class Estado(ABC):
    @abstractmethod
    def manejar(self, contexto):
        pass

    @abstractmethod
    def get_nombre(self):
        pass

class EstadoConcretoA(Estado):
    def manejar(self, contexto):
        print("Manejando en Estado A")
        contexto.estado = EstadoConcretoB()

    def get_nombre(self):
        return "Estado A"

class EstadoConcretoB(Estado):
    def manejar(self, contexto):
        print("Manejando en Estado B")
        contexto.estado = EstadoConcretoC()

    def get_nombre(self):
        return "Estado B"

class EstadoConcretoC(Estado):
    def manejar(self, contexto):
        print("Manejando en Estado C")
        contexto.estado = EstadoConcretoA()

    def get_nombre(self):
        return "Estado C"

class Contexto:
    def __init__(self, estado: Estado):
        self._estado = estado

    @property
    def estado(self):
        return self._estado

    @estado.setter
    def estado(self, estado: Estado):
        print(f"Cambiando estado de {self._estado.get_nombre()} a {estado.get_nombre()}")
        self._estado = estado

    def solicitud(self):
        self._estado.manejar(self)

# Uso
contexto = Contexto(EstadoConcretoA())

for i in range(6):
    print(f"\nSolicitud {i + 1}:")
    contexto.solicitud()
    print(f"Estado actual: {contexto.estado.get_nombre()}")

State para reproductor de música

class EstadoReproductor(ABC):
    @abstractmethod
    def reproducir(self, reproductor):
        pass

    @abstractmethod
    def pausar(self, reproductor):
        pass

    @abstractmethod
    def detener(self, reproductor):
        pass

    @abstractmethod
    def get_nombre(self):
        pass

class EstadoDetenido(EstadoReproductor):
    def reproducir(self, reproductor):
        print("▶️ Iniciando reproducción")
        reproductor.estado = EstadoReproduciendo()

    def pausar(self, reproductor):
        print("⏸️ No se puede pausar, el reproductor está detenido")

    def detener(self, reproductor):
        print("⏹️ El reproductor ya está detenido")

    def get_nombre(self):
        return "Detenido"

class EstadoReproduciendo(EstadoReproductor):
    def reproducir(self, reproductor):
        print("▶️ Ya se está reproduciendo")

    def pausar(self, reproductor):
        print("⏸️ Pausando reproducción")
        reproductor.estado = EstadoPausado()

    def detener(self, reproductor):
        print("⏹️ Deteniendo reproducción")
        reproductor.estado = EstadoDetenido()

    def get_nombre(self):
        return "Reproduciendo"

class EstadoPausado(EstadoReproductor):
    def reproducir(self, reproductor):
        print("▶️ Reanudando reproducción")
        reproductor.estado = EstadoReproduciendo()

    def pausar(self, reproductor):
        print("⏸️ Ya está pausado")

    def detener(self, reproductor):
        print("⏹️ Deteniendo desde pausa")
        reproductor.estado = EstadoDetenido()

    def get_nombre(self):
        return "Pausado"

class ReproductorMusica:
    def __init__(self):
        self.estado = EstadoDetenido()

    def reproducir(self):
        self.estado.reproducir(self)

    def pausar(self):
        self.estado.pausar(self)

    def detener(self):
        self.estado.detener(self)

    def mostrar_estado(self):
        print(f"Estado actual: {self.estado.get_nombre()}")

# Uso
reproductor = ReproductorMusica()

acciones = ['reproducir', 'pausar', 'reproducir', 'pausar', 'detener', 'pausar']

for accion in acciones:
    print(f"\n--- {accion.upper()} ---")
    if accion == 'reproducir':
        reproductor.reproducir()
    elif accion == 'pausar':
        reproductor.pausar()
    elif accion == 'detener':
        reproductor.detener()
    reproductor.mostrar_estado()

Paso 6: Template Method - Esqueleto de algoritmo

El patrón Template Method define el esqueleto de un algoritmo en una operación, delegando algunos pasos a las subclases. Permite que las subclases redefinan ciertos pasos de un algoritmo sin cambiar su estructura.

Implementación básica

from abc import ABC, abstractmethod

class ProcesadorDatos(ABC):
    # Template Method
    def procesar(self):
        self.cargar_datos()
        self.transformar_datos()
        self.validar_datos()
        self.guardar_datos()
        self.limpiar()

    @abstractmethod
    def cargar_datos(self):
        pass

    @abstractmethod
    def transformar_datos(self):
        pass

    def validar_datos(self):
        print("Validando datos (comportamiento por defecto)")

    @abstractmethod
    def guardar_datos(self):
        pass

    def limpiar(self):
        print("Limpiando recursos (comportamiento por defecto)")

class ProcesadorCSV(ProcesadorDatos):
    def cargar_datos(self):
        print("Cargando datos desde archivo CSV")

    def transformar_datos(self):
        print("Transformando datos CSV: parseando, limpiando formato")

    def guardar_datos(self):
        print("Guardando datos procesados en base de datos")

class ProcesadorJSON(ProcesadorDatos):
    def cargar_datos(self):
        print("Cargando datos desde archivo JSON")

    def transformar_datos(self):
        print("Transformando datos JSON: validando schema, convirtiendo tipos")

    def validar_datos(self):
        print("Validación especial para JSON: verificando estructura")

    def guardar_datos(self):
        print("Guardando datos procesados en API REST")

class ProcesadorXML(ProcesadorDatos):
    def cargar_datos(self):
        print("Cargando datos desde archivo XML")

    def transformar_datos(self):
        print("Transformando datos XML: parseando, mapeando elementos")

    def guardar_datos(self):
        print("Guardando datos procesados en sistema de archivos")

    def limpiar(self):
        print("Limpieza especial para XML: cerrando parsers")

# Uso
print("=== Procesando CSV ===")
procesador_csv = ProcesadorCSV()
procesador_csv.procesar()

print("\n=== Procesando JSON ===")
procesador_json = ProcesadorJSON()
procesador_json.procesar()

print("\n=== Procesando XML ===")
procesador_xml = ProcesadorXML()
procesador_xml.procesar()

Template Method para reportes

class GeneradorReporte(ABC):
    def generar_reporte(self):
        self.preparar_datos()
        self.formatear_cabecera()
        self.formatear_cuerpo()
        self.formatear_pie()
        self.finalizar_reporte()
        return self.obtener_reporte()

    def preparar_datos(self):
        print("Preparando datos para el reporte")

    @abstractmethod
    def formatear_cabecera(self):
        pass

    @abstractmethod
    def formatear_cuerpo(self):
        pass

    def formatear_pie(self):
        print("Agregando pie de página estándar")

    def finalizar_reporte(self):
        print("Finalizando reporte")

    @abstractmethod
    def obtener_reporte(self):
        pass

class ReporteHTML(GeneradorReporte):
    def __init__(self):
        self.contenido = []

    def formatear_cabecera(self):
        self.contenido.append("<html><head><title>Reporte</title></head><body>")
        self.contenido.append("<h1>Reporte HTML</h1>")

    def formatear_cuerpo(self):
        self.contenido.append("<div class='contenido'>Contenido del reporte en HTML</div>")

    def formatear_pie(self):
        self.contenido.append("<footer>Reporte generado automáticamente</footer>")
        self.contenido.append("</body></html>")

    def obtener_reporte(self):
        return "\n".join(self.contenido)

class ReportePDF(GeneradorReporte):
    def __init__(self):
        self.contenido = []

    def formatear_cabecera(self):
        self.contenido.append("=== REPORTE PDF ===")
        self.contenido.append("Título: Reporte en PDF")

    def formatear_cuerpo(self):
        self.contenido.append("Contenido del reporte en formato PDF")

    def obtener_reporte(self):
        return "\n".join(self.contenido)

class ReporteCSV(GeneradorReporte):
    def __init__(self):
        self.contenido = []

    def formatear_cabecera(self):
        self.contenido.append("columna1,columna2,columna3")

    def formatear_cuerpo(self):
        self.contenido.append("dato1,dato2,dato3")
        self.contenido.append("dato4,dato5,dato6")

    def formatear_pie(self):
        # CSV no tiene pie de página tradicional
        pass

    def obtener_reporte(self):
        return "\n".join(self.contenido)

# Uso
print("=== Generando Reporte HTML ===")
reporte_html = ReporteHTML()
html = reporte_html.generar_reporte()
print(html)

print("\n=== Generando Reporte PDF ===")
reporte_pdf = ReportePDF()
pdf = reporte_pdf.generar_reporte()
print(pdf)

print("\n=== Generando Reporte CSV ===")
reporte_csv = ReporteCSV()
csv = reporte_csv.generar_reporte()
print(csv)

Paso 7: Iterator - Recorrer colecciones

El patrón Iterator proporciona una forma de acceder secuencialmente a los elementos de un objeto agregado sin exponer su representación subyacente.

Implementación básica

from abc import ABC, abstractmethod
from typing import List, Any

class Iterador(ABC):
    @abstractmethod
    def primero(self) -> Any:
        pass

    @abstractmethod
    def siguiente(self) -> Any:
        pass

    @abstractmethod
    def ha_terminado(self) -> bool:
        pass

    @abstractmethod
    def elemento_actual(self) -> Any:
        pass

class Agregado(ABC):
    @abstractmethod
    def crear_iterador(self) -> Iterador:
        pass

# Iterador concreto para listas
class IteradorLista(Iterador):
    def __init__(self, agregado: List[Any]):
        self.agregado = agregado
        self.posicion_actual = 0

    def primero(self) -> Any:
        self.posicion_actual = 0
        return self.agregado[0] if self.agregado else None

    def siguiente(self) -> Any:
        self.posicion_actual += 1
        if not self.ha_terminado():
            return self.agregado[self.posicion_actual]
        return None

    def ha_terminado(self) -> bool:
        return self.posicion_actual >= len(self.agregado)

    def elemento_actual(self) -> Any:
        if not self.ha_terminado():
            return self.agregado[self.posicion_actual]
        return None

# Iterador con filtro
class IteradorConFiltro(Iterador):
    def __init__(self, agregado: List[Any], filtro_func):
        self.agregado = agregado
        self.filtro_func = filtro_func
        self.posicion_actual = 0
        self._filtrar_elementos()

    def _filtrar_elementos(self):
        self.elementos_filtrados = [item for item in self.agregado if self.filtro_func(item)]

    def primero(self) -> Any:
        self.posicion_actual = 0
        return self.elementos_filtrados[0] if self.elementos_filtrados else None

    def siguiente(self) -> Any:
        self.posicion_actual += 1
        if not self.ha_terminado():
            return self.elementos_filtrados[self.posicion_actual]
        return None

    def ha_terminado(self) -> bool:
        return self.posicion_actual >= len(self.elementos_filtrados)

    def elemento_actual(self) -> Any:
        if not self.ha_terminado():
            return self.elementos_filtrados[self.posicion_actual]
        return None

# Uso básico
print("=== Iterador Básico ===")
lista = [1, 2, 3, 4, 5]
iterador = IteradorLista(lista)

print("Recorriendo lista:")
item = iterador.primero()
while not iterador.ha_terminado():
    print(f"Elemento actual: {iterador.elemento_actual()}")
    iterador.siguiente()

# Uso con filtro
print("\n=== Iterador con Filtro ===")
def es_par(num):
    return num % 2 == 0

iterador_pares = IteradorConFiltro(lista, es_par)

print("Números pares:")
item = iterador_pares.primero()
while not iterador_pares.ha_terminado():
    print(f"Par: {iterador_pares.elemento_actual()}")
    iterador_pares.siguiente()

Iterator para árbol binario

class NodoArbol:
    def __init__(self, valor):
        self.valor = valor
        self.izquierda = None
        self.derecha = None

class ArbolBinario:
    def __init__(self):
        self.raiz = None

    def insertar(self, valor):
        if self.raiz is None:
            self.raiz = NodoArbol(valor)
        else:
            self._insertar_recursivo(self.raiz, valor)

    def _insertar_recursivo(self, nodo, valor):
        if valor < nodo.valor:
            if nodo.izquierda is None:
                nodo.izquierda = NodoArbol(valor)
            else:
                self._insertar_recursivo(nodo.izquierda, valor)
        else:
            if nodo.derecha is None:
                nodo.derecha = NodoArbol(valor)
            else:
                self._insertar_recursivo(nodo.derecha, valor)

    def crear_iterador_inorden(self):
        return IteradorInorden(self.raiz)

    def crear_iterador_preorden(self):
        return IteradorPreorden(self.raiz)

    def crear_iterador_postorden(self):
        return IteradorPostorden(self.raiz)

class IteradorArbol(Iterador):
    def __init__(self, raiz):
        self.raiz = raiz
        self.pila = []
        self._inicializar()

    @abstractmethod
    def _inicializar(self):
        pass

    def primero(self):
        self._inicializar()
        return self.elemento_actual()

    def siguiente(self):
        if self.ha_terminado():
            return None
        self._avanzar()
        return self.elemento_actual()

    @abstractmethod
    def _avanzar(self):
        pass

    def ha_terminado(self):
        return len(self.pila) == 0

    def elemento_actual(self):
        if not self.ha_terminado():
            return self.pila[-1].valor
        return None

class IteradorInorden(IteradorArbol):
    def _inicializar(self):
        self.pila = []
        self._ir_mas_izquierda(self.raiz)

    def _ir_mas_izquierda(self, nodo):
        while nodo:
            self.pila.append(nodo)
            nodo = nodo.izquierda

    def _avanzar(self):
        nodo = self.pila.pop()
        if nodo.derecha:
            self._ir_mas_izquierda(nodo.derecha)

class IteradorPreorden(IteradorArbol):
    def _inicializar(self):
        self.pila = []
        if self.raiz:
            self.pila.append(self.raiz)

    def _avanzar(self):
        nodo = self.pila.pop()
        if nodo.derecha:
            self.pila.append(nodo.derecha)
        if nodo.izquierda:
            self.pila.append(nodo.izquierda)

class IteradorPostorden(IteradorArbol):
    def _inicializar(self):
        self.pila = []
        self._preparar_postorden(self.raiz)

    def _preparar_postorden(self, nodo):
        if nodo:
            self.pila.append(nodo)
            if nodo.derecha:
                self._preparar_postorden(nodo.derecha)
            if nodo.izquierda:
                self._preparar_postorden(nodo.izquierda)

    def _avanzar(self):
        self.pila.pop()

# Uso del árbol
arbol = ArbolBinario()
valores = [5, 3, 7, 2, 4, 6, 8]
for valor in valores:
    arbol.insertar(valor)

print("=== Recorrido Inorden (ordenado) ===")
iterador = arbol.crear_iterador_inorden()
item = iterador.primero()
while not iterador.ha_terminado():
    print(f"Valor: {iterador.elemento_actual()}")
    iterador.siguiente()

print("\n=== Recorrido Preorden ===")
iterador = arbol.crear_iterador_preorden()
item = iterador.primero()
while not iterador.ha_terminado():
    print(f"Valor: {iterador.elemento_actual()}")
    iterador.siguiente()

print("\n=== Recorrido Postorden ===")
iterador = arbol.crear_iterador_postorden()
item = iterador.primero()
while not iterador.ha_terminado():
    print(f"Valor: {iterador.elemento_actual()}")
    iterador.siguiente()

Paso 8: Mediator - Comunicación centralizada

El patrón Mediator define un objeto que encapsula cómo un conjunto de objetos interactúa. Promueve el acoplamiento débil al evitar que los objetos se refieran entre sí explícitamente.

Implementación básica

from abc import ABC, abstractmethod
from typing import Dict, List

class Colega(ABC):
    def __init__(self, mediador, nombre: str):
        self.mediador = mediador
        self.nombre = nombre

    @abstractmethod
    def enviar(self, mensaje: str):
        pass

    @abstractmethod
    def recibir(self, mensaje: str, remitente: str):
        pass

class Mediador(ABC):
    @abstractmethod
    def registrar_colega(self, colega: Colega):
        pass

    @abstractmethod
    def enviar_mensaje(self, mensaje: str, remitente: Colega, destinatario: str = None):
        pass

class MediadorConcreto(Mediador):
    def __init__(self):
        self.colegas: Dict[str, Colega] = {}

    def registrar_colega(self, colega: Colega):
        self.colegas[colega.nombre] = colega

    def enviar_mensaje(self, mensaje: str, remitente: Colega, destinatario: str = None):
        if destinatario:
            # Mensaje privado
            if destinatario in self.colegas:
                self.colegas[destinatario].recibir(mensaje, remitente.nombre)
            else:
                print(f"❌ Destinatario '{destinatario}' no encontrado")
        else:
            # Broadcast a todos excepto remitente
            for nombre, colega in self.colegas.items():
                if nombre != remitente.nombre:
                    colega.recibir(mensaje, remitente.nombre)

class ColegaConcreto(Colega):
    def enviar(self, mensaje: str, destinatario: str = None):
        print(f"📤 {self.nombre} enviando mensaje: '{mensaje}'")
        self.mediador.enviar_mensaje(mensaje, self, destinatario)

    def recibir(self, mensaje: str, remitente: str):
        print(f"📥 {self.nombre} recibió de {remitente}: '{mensaje}'")

# Uso
mediador = MediadorConcreto()

# Crear colegas
colega1 = ColegaConcreto(mediador, "Usuario1")
colega2 = ColegaConcreto(mediador, "Usuario2") 
colega3 = ColegaConcreto(mediador, "Usuario3")
colega4 = ColegaConcreto(mediador, "Usuario4")

# Registrar colegas
mediador.registrar_colega(colega1)
mediador.registrar_colega(colega2)
mediador.registrar_colega(colega3)
mediador.registrar_colega(colega4)

print("=== Comunicación con Mediador ===")

# Mensajes broadcast
colega1.enviar("¡Hola a todos!")
colega2.enviar("¡Hola! ¿Cómo están?")

# Mensajes privados
colega3.enviar("Mensaje secreto para Usuario1", "Usuario1")
colega1.enviar("Respuesta privada", "Usuario3")

# Usuario no existente
colega2.enviar("Mensaje para usuario inexistente", "Usuario99")

Mediator para sistema de chat

class UsuarioChat(Colega):
    def __init__(self, mediador, nombre: str, sala: str):
        super().__init__(mediador, nombre)
        self.sala = sala

    def enviar(self, mensaje: str, destinatario: str = None):
        print(f"💬 {self.nombre} en {self.sala}: '{mensaje}'")
        self.mediador.enviar_mensaje(mensaje, self, destinatario)

    def recibir(self, mensaje: str, remitente: str):
        print(f"💭 {self.nombre} recibió de {remitente}: '{mensaje}'")

    def unirse_sala(self, sala: str):
        print(f"🚪 {self.nombre} se unió a {sala}")
        self.sala = sala
        self.mediador.actualizar_sala(self, sala)

    def salir_sala(self):
        print(f"🚪 {self.nombre} salió de {self.sala}")
        self.mediador.actualizar_sala(self, None)

class MediadorChat(MediadorConcreto):
    def __init__(self):
        super().__init__()
        self.salas: Dict[str, List[Colega]] = {}

    def registrar_colega(self, colega: UsuarioChat):
        super().registrar_colega(colega)
        self.agregar_a_sala(colega, colega.sala)

    def agregar_a_sala(self, colega: UsuarioChat, sala: str):
        if sala not in self.salas:
            self.salas[sala] = []
        self.salas[sala].append(colega)

    def remover_de_sala(self, colega: UsuarioChat, sala: str):
        if sala in self.salas and colega in self.salas[sala]:
            self.salas[sala].remove(colega)

    def actualizar_sala(self, colega: UsuarioChat, nueva_sala: str):
        # Remover de sala actual
        if colega.sala in self.salas:
            self.remover_de_sala(colega, colega.sala)

        # Agregar a nueva sala
        if nueva_sala:
            self.agregar_a_sala(colega, nueva_sala)

    def enviar_mensaje(self, mensaje: str, remitente: Colega, destinatario: str = None):
        if destinatario:
            # Mensaje privado
            super().enviar_mensaje(mensaje, remitente, destinatario)
        else:
            # Mensaje a toda la sala
            sala_remitente = remitente.sala
            if sala_remitente in self.salas:
                for colega in self.salas[sala_remitente]:
                    if colega.nombre != remitente.nombre:
                        colega.recibir(mensaje, remitente.nombre)

# Uso del sistema de chat
print("\n=== Sistema de Chat con Salas ===")
mediador_chat = MediadorChat()

# Crear usuarios
usuario1 = UsuarioChat(mediador_chat, "Ana", "general")
usuario2 = UsuarioChat(mediador_chat, "Carlos", "general")
usuario3 = UsuarioChat(mediador_chat, "Maria", "tecnologia")
usuario4 = UsuarioChat(mediador_chat, "Pedro", "tecnologia")

# Registrar usuarios
mediador_chat.registrar_colega(usuario1)
mediador_chat.registrar_colega(usuario2)
mediador_chat.registrar_colega(usuario3)
mediador_chat.registrar_colega(usuario4)

# Mensajes en salas
usuario1.enviar("¡Hola a todos en general!")
usuario3.enviar("Discutiendo sobre Python en tecnología")

# Cambio de sala
usuario2.unirse_sala("tecnologia")
usuario2.enviar("¡Hola! Me uní a tecnología")

# Mensaje privado
usuario1.enviar("Mensaje privado para Maria", "Maria")

# Mostrar estado de salas
print("\n=== Estado de Salas ===")
for sala, usuarios in mediador_chat.salas.items():
    nombres = [u.nombre for u in usuarios]
    print(f"Sala '{sala}': {nombres}")

Paso 9: Memento - Guardar y restaurar estado

El patrón Memento captura y externaliza el estado interno de un objeto sin violar la encapsulación, para que el objeto pueda ser restaurado a este estado más tarde.

Implementación básica

from abc import ABC, abstractmethod
from typing import Any

class Memento:
    def __init__(self, estado: Any):
        self._estado = estado

    def obtener_estado(self):
        return self._estado

class Originador:
    def __init__(self):
        self._estado = None

    def establecer_estado(self, estado: Any):
        print(f"Estableciendo estado: {estado}")
        self._estado = estado

    def obtener_estado(self):
        return self._estado

    def guardar_en_memento(self) -> Memento:
        print(f"Guardando estado en memento: {self._estado}")
        return Memento(self._estado)

    def restaurar_desde_memento(self, memento: Memento):
        self._estado = memento.obtener_estado()
        print(f"Estado restaurado desde memento: {self._estado}")

class Cuidador:
    def __init__(self, originador: Originador):
        self.originador = originador
        self.mementos = []

    def guardar(self):
        memento = self.originador.guardar_en_memento()
        self.mementos.append(memento)
        print(f"Memento guardado. Total: {len(self.mementos)}")

    def deshacer(self):
        if self.mementos:
            memento = self.mementos.pop()
            self.originador.restaurar_desde_memento(memento)
            return True
        return False

# Uso
originador = Originador()
cuidador = Cuidador(originador)

originador.establecer_estado("Estado 1")
cuidador.guardar()

originador.establecer_estado("Estado 2")
cuidador.guardar()

originador.establecer_estado("Estado 3")
cuidador.guardar()

print(f"Estado actual: {originador.obtener_estado()}")

cuidador.deshacer()
cuidador.deshacer()

print(f"Estado después de deshacer 2 veces: {originador.obtener_estado()}")

Memento para editor de texto

class EstadoDocumento:
    def __init__(self, contenido, cursor_posicion):
        self.contenido = contenido
        self.cursor_posicion = cursor_posicion

class EditorTexto:
    def __init__(self):
        self.contenido = ""
        self.cursor_posicion = 0

    def escribir_texto(self, texto):
        # Insertar texto en la posición del cursor
        self.contenido = (self.contenido[:self.cursor_posicion] + 
                         texto + 
                         self.contenido[self.cursor_posicion:])
        self.cursor_posicion += len(texto)
        print(f"📝 Escrito: '{texto}'")

    def borrar_caracter(self):
        if self.cursor_posicion > 0:
            borrado = self.contenido[self.cursor_posicion - 1]
            self.contenido = (self.contenido[:self.cursor_posicion - 1] + 
                             self.contenido[self.cursor_posicion:])
            self.cursor_posicion -= 1
            print(f"⌫ Borrado: '{borrado}'")
            return borrado
        return None

    def mover_cursor(self, posicion):
        if 0 <= posicion <= len(self.contenido):
            self.cursor_posicion = posicion
            print(f"📍 Cursor movido a posición {posicion}")

    def obtener_estado(self):
        return {
            'contenido': self.contenido,
            'cursor': self.cursor_posicion
        }

    def mostrar_estado(self):
        contenido_mostrado = self.contenido
        if self.cursor_posicion < len(contenido_mostrado):
            contenido_mostrado = (contenido_mostrado[:self.cursor_posicion] + 
                                 '|' + 
                                 contenido_mostrado[self.cursor_posicion:])
        else:
            contenido_mostrado += '|'
        print(f"📄 Contenido: '{contenido_mostrado}'")

class HistorialEditor:
    def __init__(self, editor):
        self.editor = editor
        self.historial = []

    def guardar_estado(self):
        estado = EstadoDocumento(self.editor.contenido, self.editor.cursor_posicion)
        self.historial.append(estado)
        print(f"💾 Estado guardado (total: {len(self.historial)})")

    def deshacer(self):
        if self.historial:
            estado = self.historial.pop()
            self.editor.contenido = estado.contenido
            self.editor.cursor_posicion = estado.cursor_posicion
            print("🔄 Deshecho último cambio")
            return True
        print("❌ No hay cambios para deshacer")
        return False

# Uso
editor = EditorTexto()
historial = HistorialEditor(editor)

editor.mostrar_estado()

editor.escribir_texto("Hola")
historial.guardar_estado()
editor.mostrar_estado()

editor.escribir_texto(" mundo")
historial.guardar_estado()
editor.mostrar_estado()

editor.borrar_caracter()
editor.mostrar_estado()

print("\n=== Deshaciendo ===")
historial.deshacer()
editor.mostrar_estado()

historial.deshacer()
editor.mostrar_estado()

Paso 10: Comparación y selección de patrones

Cuándo usar cada patrón de comportamiento

Patrón Cuándo usarlo Ventajas Desventajas
Observer Notificaciones, eventos Bajo acoplamiento, extensible Puede ser complejo de debuggear
Strategy Algoritmos variables Fácil cambio en runtime Aumenta número de clases
Command Operaciones reversibles, colas Deshacer, logging Verbose para operaciones simples
State Estados complejos Modelo claro de estados Puede crear muchas clases
Template Method Algoritmos con pasos variables Reutilización, control Puede violar Liskov Substitution
Iterator Recorrer colecciones Independiente de estructura Puede ser menos eficiente
Mediator Comunicación compleja Reduce dependencias Puede volverse bottleneck
Memento Deshacer, snapshots Simple implementación Puede consumir mucha memoria

Paso 11: Proyecto práctico - Editor de texto

Vamos a crear un editor de texto completo que combine múltiples patrones de comportamiento.

from abc import ABC, abstractmethod
import time

# Memento para estados del documento
class EstadoDocumento:
    def __init__(self, contenido, cursor_posicion, seleccion=None):
        self.contenido = contenido
        self.cursor_posicion = cursor_posicion
        self.seleccion = seleccion or (0, 0)

# Command para operaciones del editor
class ComandoEditor(ABC):
    def __init__(self, editor):
        self.editor = editor

    @abstractmethod
    def ejecutar(self):
        pass

    @abstractmethod
    def deshacer(self):
        pass

class ComandoEscribir(ComandoEditor):
    def __init__(self, editor, texto, posicion=None):
        super().__init__(editor)
        self.texto = texto
        self.posicion = posicion
        self.texto_anterior = None

    def ejecutar(self):
        if self.posicion is not None:
            self.editor.mover_cursor(self.posicion)

        # Guardar texto que será reemplazado
        longitud_texto = len(self.texto)
        inicio = self.editor.cursor_posicion
        fin = inicio + longitud_texto
        self.texto_anterior = self.editor.contenido[inicio:fin]

        # Insertar texto
        self.editor.contenido = (self.editor.contenido[:inicio] + 
                                self.texto + 
                                self.editor.contenido[fin:])
        self.editor.cursor_posicion = inicio + longitud_texto

    def deshacer(self):
        # Remover el texto insertado y restaurar el anterior
        inicio = self.editor.cursor_posicion - len(self.texto)
        fin = self.editor.cursor_posicion
        self.editor.contenido = (self.editor.contenido[:inicio] + 
                                self.texto_anterior + 
                                self.editor.contenido[fin:])
        self.editor.cursor_posicion = inicio + len(self.texto_anterior)

class ComandoBorrar(ComandoEditor):
    def __init__(self, editor, cantidad=1):
        super().__init__(editor)
        self.cantidad = cantidad
        self.texto_borrado = None
        self.posicion_original = None

    def ejecutar(self):
        if self.editor.cursor_posicion >= self.cantidad:
            self.posicion_original = self.editor.cursor_posicion
            inicio = self.editor.cursor_posicion - self.cantidad
            fin = self.editor.cursor_posicion

            self.texto_borrado = self.editor.contenido[inicio:fin]
            self.editor.contenido = (self.editor.contenido[:inicio] + 
                                   self.editor.contenido[fin:])
            self.editor.cursor_posicion = inicio

    def deshacer(self):
        if self.texto_borrado and self.posicion_original is not None:
            posicion_insercion = self.editor.cursor_posicion
            self.editor.contenido = (self.editor.contenido[:posicion_insercion] + 
                                   self.texto_borrado + 
                                   self.editor.contenido[posicion_insercion:])
            self.editor.cursor_posicion = self.posicion_original

# Observer para notificaciones de cambios
class ObservadorEditor(ABC):
    @abstractmethod
    def actualizar(self, editor, evento):
        pass

class ObservadorConsola(ObservadorEditor):
    def actualizar(self, editor, evento):
        if evento == "contenido_cambiado":
            print(f"📝 Contenido actualizado. Longitud: {len(editor.contenido)}")
        elif evento == "cursor_movido":
            print(f"📍 Cursor en posición: {editor.cursor_posicion}")

class ObservadorArchivo(ObservadorEditor):
    def __init__(self, nombre_archivo):
        self.nombre_archivo = nombre_archivo
        self.ultimo_guardado = 0

    def actualizar(self, editor, evento):
        if evento == "contenido_cambiado":
            # Auto-guardar cada cierto tiempo
            tiempo_actual = time.time()
            if tiempo_actual - self.ultimo_guardado > 5:  # 5 segundos
                self._guardar_archivo(editor)
                self.ultimo_guardado = tiempo_actual

    def _guardar_archivo(self, editor):
        # Simular guardado
        print(f"💾 Auto-guardando en {self.nombre_archivo}")

# State para modos del editor
class EstadoEditor(ABC):
    @abstractmethod
    def manejar_tecla(self, editor, tecla):
        pass

    @abstractmethod
    def obtener_nombre(self):
        pass

class EstadoInsercion(EstadoEditor):
    def manejar_tecla(self, editor, tecla):
        if tecla == "Escape":
            editor.cambiar_estado(EstadoNormal())
        elif tecla == "Backspace":
            editor.ejecutar_comando(ComandoBorrar(editor))
        elif len(tecla) == 1:  # Carácter normal
            editor.ejecutar_comando(ComandoEscribir(editor, tecla))

    def obtener_nombre(self):
        return "INSERTAR"

class EstadoNormal(EstadoEditor):
    def manejar_tecla(self, editor, tecla):
        if tecla == "i":
            editor.cambiar_estado(EstadoInsercion())
        elif tecla == "x":
            editor.ejecutar_comando(ComandoBorrar(editor))
        elif tecla == "u":
            editor.deshacer()

    def obtener_nombre(self):
        return "NORMAL"

# Editor principal
class EditorTexto:
    def __init__(self):
        self.contenido = ""
        self.cursor_posicion = 0
        self.estado = EstadoNormal()
        self.observadores = []
        self.historial_comandos = []
        self.indice_historial = -1

    def agregar_observador(self, observador):
        self.observadores.append(observador)

    def notificar_observadores(self, evento):
        for observador in self.observadores:
            observador.actualizar(self, evento)

    def cambiar_estado(self, nuevo_estado):
        self.estado = nuevo_estado
        print(f"🔄 Modo cambiado a: {self.estado.obtener_nombre()}")

    def ejecutar_comando(self, comando):
        comando.ejecutar()
        # Agregar al historial (remover comandos "futuros" si estamos en medio del historial)
        self.historial_comandos = self.historial_comandos[:self.indice_historial + 1]
        self.historial_comandos.append(comando)
        self.indice_historial += 1
        self.notificar_observadores("contenido_cambiado")

    def deshacer(self):
        if self.indice_historial >= 0:
            comando = self.historial_comandos[self.indice_historial]
            comando.deshacer()
            self.indice_historial -= 1
            self.notificar_observadores("contenido_cambiado")
            print("🔄 Comando deshecho")

    def mover_cursor(self, posicion):
        if 0 <= posicion <= len(self.contenido):
            self.cursor_posicion = posicion
            self.notificar_observadores("cursor_movido")

    def procesar_tecla(self, tecla):
        self.estado.manejar_tecla(self, tecla)

    def mostrar_estado(self):
        contenido_mostrado = self.contenido
        if self.cursor_posicion < len(contenido_mostrado):
            contenido_mostrado = (contenido_mostrado[:self.cursor_posicion] + 
                                 '|' + 
                                 contenido_mostrado[self.cursor_posicion:])
        else:
            contenido_mostrado += '|'

        print(f"📄 [{self.estado.obtener_nombre()}] '{contenido_mostrado}'")

# Strategy para diferentes estrategias de guardado
class EstrategiaGuardado(ABC):
    @abstractmethod
    def guardar(self, contenido, nombre_archivo):
        pass

class GuardadoLocal(EstrategiaGuardado):
    def guardar(self, contenido, nombre_archivo):
        print(f"💾 Guardando localmente en {nombre_archivo}")
        # Simular guardado
        print(f"   Contenido: {contenido[:50]}...")

class GuardadoNube(EstrategiaGuardado):
    def guardar(self, contenido, nombre_archivo):
        print(f"☁️ Guardando en la nube: {nombre_archivo}")
        # Simular guardado en nube
        print(f"   Subiendo {len(contenido)} caracteres...")

# Demo del editor completo
def demo_editor():
    # Crear editor
    editor = EditorTexto()

    # Agregar observadores
    observador_consola = ObservadorConsola()
    observador_archivo = ObservadorArchivo("documento.txt")

    editor.agregar_observador(observador_consola)
    editor.agregar_observador(observador_archivo)

    print("=== Editor de Texto Interactivo ===")
    editor.mostrar_estado()

    # Simular entrada del usuario
    comandos = [
        "i",        # Entrar en modo inserción
        "H", "o", "l", "a", " ", "m", "u", "n", "d", "o",
        "Escape",   # Volver a modo normal
        "x",        # Borrar carácter
        "u",        # Deshacer
    ]

    for comando in comandos:
        print(f"\n🎹 Presionando tecla: '{comando}'")
        editor.procesar_tecla(comando)
        editor.mostrar_estado()
        time.sleep(0.1)  # Pequeña pausa para simular tiempo real

    print("\n=== Estrategias de Guardado ===")

    # Strategy para guardado
    estrategias = [GuardadoLocal(), GuardadoNube()]

    for estrategia in estrategias:
        estrategia.guardar(editor.contenido, "mi_documento.txt")

if __name__ == "__main__":
    demo_editor()

Conclusión

¡Has dominado los patrones de diseño de comportamiento! Estos patrones te permiten gestionar algoritmos, estados y comunicaciones entre objetos de manera flexible y mantenible.

Practica aplicando estos patrones en proyectos reales y combina diferentes patrones según las necesidades específicas de tu aplicación.

Para más tutoriales sobre patrones de diseño de comportamiento avanzados, visita nuestra sección de tutoriales.


¡Sigue practicando y aplicando estos patrones en proyectos reales!


💡 Tip Importante

📝 Mejores Prácticas en Patrones de Comportamiento

  • Elige el patrón adecuado: Analiza el problema de comunicación/algoritmo antes de seleccionar un patrón
  • Principio de responsabilidad única: Cada clase debe tener una sola razón para cambiar
  • Principio abierto/cerrado: Los patrones deben estar abiertos a extensión pero cerrados a modificación
  • Composición sobre herencia: Prefiere composición cuando sea posible
  • Testing: Los patrones deben facilitar el testing unitario
  • Documenta decisiones: Explica por qué elegiste ciertos patrones en tu código
  • Evita over-engineering: No uses patrones complejos para problemas simples
  • Considera el rendimiento: Algunos patrones pueden afectar el rendimiento

📚 Recursos Recomendados:

¡Estos patrones te ayudarán a crear software con mejor comportamiento y comunicación!

Toca los botones para interactuar

Comentarios

Comentarios

Inicia sesión para dejar un comentario.

No hay comentarios aún

Sé el primero en comentar este tutorial.

Tutoriales Relacionados

Descubre más tutoriales relacionados que podrían ser de tu interés

Imagen destacada del tutorial relacionado: Principios SOLID de Diseño de Software
Patrones de Diseño

Principios SOLID de Diseño de Software

Aprende los principios SOLID de diseño de software (SRP, OCP, LSP, ISP, DIP) con ejemplos prácticos en Python y mejora la calidad de tu código.

José Elías Romero Guanipa
01 Sep 2025
Imagen destacada del tutorial relacionado: Patrones de Diseño Creacionales
Patrones de Diseño

Patrones de Diseño Creacionales

Aprende patrones de diseño creacionales como Singleton, Factory, Builder y Prototype con ejemplos prácticos en Python.

José Elías Romero Guanipa
03 Sep 2025
Imagen destacada del tutorial relacionado: Patrones de Diseño Estructurales
Patrones de Diseño

Patrones de Diseño Estructurales

Aprende patrones de diseño estructurales como Adapter, Decorator, Facade y Proxy con ejemplos prácticos en Python.

José Elías Romero Guanipa
04 Sep 2025
Imagen destacada del tutorial relacionado: Patrones de Diseño - Aplicaciones Avanzadas
Patrones de Diseño

Patrones de Diseño - Aplicaciones Avanzadas

Aprende aplicaciones avanzadas de patrones de diseño en arquitecturas empresariales como microservicios, CQRS, event sourcing y más con ejemplos prácticos en Python.

José Elías Romero Guanipa
05 Sep 2025
Foto de perfil del autor José Elías Romero Guanipa
José Elías Romero Guanipa
Autor

🌟 Nube de Etiquetas

Descubre temas populares en nuestros tutoriales

python
python 30 tutoriales
poo
poo 8 tutoriales
ciencia de datos
ciencia de datos 8 tutoriales
patrones diseño
patrones diseño 7 tutoriales
matplotlib
matplotlib 7 tutoriales
pandas
pandas 6 tutoriales
visualizacion
visualizacion 6 tutoriales
principiante
principiante 5 tutoriales
numpy
numpy 5 tutoriales
estadistica
estadistica 4 tutoriales
bases de datos
bases de datos 4 tutoriales
dataframe
dataframe 4 tutoriales
csv
csv 3 tutoriales
json
json 3 tutoriales
machine learning
machine learning 3 tutoriales
rendimiento
rendimiento 3 tutoriales
mysql
mysql 3 tutoriales
postgresql
postgresql 3 tutoriales
analisis de datos
analisis de datos 3 tutoriales
graficos
graficos 3 tutoriales
excepciones
excepciones 2 tutoriales
algoritmos
algoritmos 2 tutoriales
estructuras datos
estructuras datos 2 tutoriales
programación
programación 2 tutoriales
colaboracion
colaboracion 2 tutoriales

Las etiquetas más grandes y brillantes aparecen en más tutoriales

logo logo

©2024 ViveBTC