
Fundamentos de Programación: Patrones de Diseño de Comportamiento
Aprende patrones de diseño de comportamiento como Observer, Strategy, Command y State con ejemplos prácticos en Python.
¡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?
- Paso 2: Observer - Notificaciones de cambios
- Paso 3: Strategy - Algoritmos intercambiables
- Paso 4: Command - Encapsular operaciones
- Paso 5: State - Cambio de comportamiento por estado
- Paso 6: Template Method - Esqueleto de algoritmo
- Paso 7: Iterator - Recorrer colecciones
- Paso 8: Mediator - Comunicación centralizada
- Paso 9: Memento - Guardar y restaurar estado
- Paso 10: Comparación y selección de patrones
- Paso 11: Proyecto práctico - Editor de texto
- Conclusión
- 💡 Tip Importante
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:
- Design Patterns: Elements of Reusable Object-Oriented Software - Gang of Four
- Head First Design Patterns - Libro práctico
- Refactoring.Guru - Behavioral Patterns - Ejemplos interactivos
¡Estos patrones te ayudarán a crear software con mejor comportamiento y comunicación!
No hay comentarios aún
Sé el primero en comentar este tutorial.