Imagen destacada del tutorial: Patrones de Diseño Estructurales
Patrones de Diseño

Patrones de Diseño Estructurales

José Elías Romero Guanipa
04 Sep 2025

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

patrones diseño patrones estructurales adapter decorator facade +5 más

¡Domina los patrones de diseño estructurales! En este tutorial especializado te guiaré paso a paso para que aprendas los patrones fundamentales de estructura, incluyendo Adapter, Decorator, Facade, Proxy, Bridge y Composite, con ejemplos prácticos y casos de uso reales en Python.

Objetivo: Aprender los patrones de diseño estructurales más importantes, sus implementaciones en Python, ventajas, desventajas y cuándo aplicarlos para organizar y simplificar la estructura de tus aplicaciones.

Índice

Paso 1: ¿Qué son los patrones estructurales?

Los patrones de diseño estructurales se centran en cómo las clases y objetos se componen para formar estructuras más grandes. Estos patrones ayudan a asegurar que cuando una parte del sistema cambia, el resto del sistema no se vea afectado.

¿Por qué son importantes?

  • Flexibilidad: Permiten cambiar la estructura sin afectar otras partes
  • Reutilización: Facilitan la composición de objetos
  • Mantenimiento: Simplifican la modificación de estructuras complejas
  • Abstracción: Ocultan complejidad interna

Clasificación de patrones estructurales

Patrón Propósito Complejidad
Adapter Conectar interfaces incompatibles Media
Decorator Añadir funcionalidad dinámicamente Baja
Facade Interfaz simplificada Baja
Proxy Control de acceso Media
Bridge Separar abstracción e implementación Alta
Composite Estructuras de árbol Media
Flyweight Compartir objetos eficientemente Alta

Paso 2: Adapter - Conectar interfaces incompatibles

El patrón Adapter permite que clases con interfaces incompatibles trabajen juntas, actuando como un puente entre diferentes interfaces sin modificar el código existente.

Implementación básica (Adapter de clase)

# Sistema existente que queremos adaptar
class ServicioWebAntiguo:
    def obtener_datos_antiguos(self):
        return {
            "nombre_completo": "Ana García",
            "edad_persona": 28,
            "correo_usuario": "[email protected]",
            "telefono_contacto": "+123456789"
        }

# Nueva interfaz que esperamos
class ClienteDatos:
    def obtener_datos_usuario(self):
        # Debe devolver: {"nombre": str, "edad": int, "email": str, "telefono": str}
        pass

# Adapter que convierte la interfaz antigua a la nueva
class AdapterServicioWeb(ClienteDatos):
    def __init__(self, servicio_antiguo):
        self.servicio_antiguo = servicio_antiguo

    def obtener_datos_usuario(self):
        datos_antiguos = self.servicio_antiguo.obtener_datos_antiguos()

        # Transformar los datos al nuevo formato
        return {
            "nombre": datos_antiguos["nombre_completo"],
            "edad": datos_antiguos["edad_persona"],
            "email": datos_antiguos["correo_usuario"],
            "telefono": datos_antiguos["telefono_contacto"]
        }

# Uso del adapter
servicio_antiguo = ServicioWebAntiguo()
adapter = AdapterServicioWeb(servicio_antiguo)

cliente = ClienteDatos()
cliente = adapter  # El cliente ahora puede usar el servicio antiguo a través del adapter

datos_usuario = cliente.obtener_datos_usuario()
print(datos_usuario)
# {"nombre": "Ana García", "edad": 28, "email": "[email protected]", "telefono": "+123456789"}

Adapter de objeto

from abc import ABC, abstractmethod

# Target interface
class Notificador(ABC):
    @abstractmethod
    def enviar_notificacion(self, mensaje, destinatario):
        pass

# Adaptee (clase existente incompatible)
class ServicioEmailLegacy:
    def enviar_correo(self, destinatario, asunto, cuerpo):
        print(f"Enviando correo a {destinatario}")
        print(f"Asunto: {asunto}")
        print(f"Cuerpo: {cuerpo}")

# Adapter
class AdaptadorEmail(Notificador):
    def __init__(self, servicio_email):
        self.servicio_email = servicio_email

    def enviar_notificacion(self, mensaje, destinatario):
        # Adaptar la interfaz
        self.servicio_email.enviar_correo(
            destinatario=destinatario,
            asunto="Notificación del Sistema",
            cuerpo=mensaje
        )

# Uso
servicio_email = ServicioEmailLegacy()
notificador = AdaptadorEmail(servicio_email)

# Ahora podemos usar el servicio legacy con la nueva interfaz
notificador.enviar_notificacion("Bienvenido al sistema", "[email protected]")

Caso real: Integración con APIs externas

import requests
from abc import ABC, abstractmethod

# Nuestra interfaz estándar
class ProveedorPagos(ABC):
    @abstractmethod
    def procesar_pago(self, monto, tarjeta, descripcion):
        pass

# API externa con interfaz diferente
class ApiPagoExterna:
    def charge(self, amount_in_cents, card_token, metadata):
        # Simular llamada a API externa
        response = {
            "status": "success",
            "transaction_id": "txn_123456",
            "amount": amount_in_cents / 100
        }
        return response

# Adapter para la API externa
class AdaptadorPagoExterno(ProveedorPagos):
    def __init__(self, api_externa):
        self.api_externa = api_externa

    def procesar_pago(self, monto, tarjeta, descripcion):
        # Convertir parámetros al formato de la API externa
        amount_in_cents = int(monto * 100)
        metadata = {"description": descripcion}

        # Llamar a la API externa
        resultado = self.api_externa.charge(amount_in_cents, tarjeta, metadata)

        # Convertir respuesta al formato esperado
        return {
            "exitoso": resultado["status"] == "success",
            "id_transaccion": resultado["transaction_id"],
            "monto": resultado["amount"]
        }

# Uso
api_externa = ApiPagoExterna()
procesador_pagos = AdaptadorPagoExterno(api_externa)

resultado = procesador_pagos.procesar_pago(99.99, "tok_visa_123", "Compra en tienda")
print(resultado)

Paso 3: Decorator - Añadir funcionalidad dinámicamente

El patrón Decorator permite añadir responsabilidades adicionales a un objeto de manera dinámica, proporcionando una alternativa flexible a la herencia para extender funcionalidades.

Implementación básica

from abc import ABC, abstractmethod

# Componente base
class Bebida(ABC):
    @abstractmethod
    def costo(self):
        pass

    @abstractmethod
    def descripcion(self):
        pass

# Componente concreto
class Cafe(Bebida):
    def costo(self):
        return 2.0

    def descripcion(self):
        return "Café"

# Decorator base
class DecoradorBebida(Bebida):
    def __init__(self, bebida):
        self.bebida = bebida

    def costo(self):
        return self.bebida.costo()

    def descripcion(self):
        return self.bebida.descripcion()

# Decorators concretos
class Leche(DecoradorBebida):
    def costo(self):
        return self.bebida.costo() + 0.5

    def descripcion(self):
        return self.bebida.descripcion() + " con leche"

class Azucar(DecoradorBebida):
    def costo(self):
        return self.bebida.costo() + 0.2

    def descripcion(self):
        return self.bebida.descripcion() + " con azúcar"

class Canela(DecoradorBebida):
    def costo(self):
        return self.bebida.costo() + 0.3

    def descripcion(self):
        return self.bebida.descripcion() + " con canela"

# Uso
bebida = Cafe()
print(f"{bebida.descripcion()}: ${bebida.costo()}")

bebida = Leche(bebida)
print(f"{bebida.descripcion()}: ${bebida.costo()}")

bebida = Azucar(bebida)
print(f"{bebida.descripcion()}: ${bebida.costo()}")

bebida = Canela(bebida)
print(f"{bebida.descripcion()}: ${bebida.costo()}")

Decorator funcional (usando funciones)

def decorador_logging(func):
    def wrapper(*args, **kwargs):
        print(f"Llamando a {func.__name__} con args: {args}, kwargs: {kwargs}")
        resultado = func(*args, **kwargs)
        print(f"Resultado: {resultado}")
        return resultado
    return wrapper

def decorador_caching(func):
    cache = {}

    def wrapper(*args, **kwargs):
        clave = str(args) + str(sorted(kwargs.items()))
        if clave not in cache:
            print(f"Calculando resultado para {clave}")
            cache[clave] = func(*args, **kwargs)
        else:
            print(f"Usando resultado cacheado para {clave}")
        return cache[clave]
    return wrapper

@decorador_logging
@decorador_caching
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

# Uso
print(fibonacci(5))
print(fibonacci(5))  # Usará cache

Decorator para logging y timing

import time
from functools import wraps

def timing_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"{func.__name__} tomó {end_time - start_time:.4f} segundos")
        return result
    return wrapper

def logging_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f"Llamando {func.__name__} con argumentos: {args}")
        result = func(*args, **kwargs)
        print(f"{func.__name__} retornó: {result}")
        return result
    return wrapper

@timing_decorator
@logging_decorator
def proceso_complejo(n):
    time.sleep(0.1)  # Simular trabajo
    return sum(range(n))

# Uso
resultado = proceso_complejo(1000)

Paso 4: Facade - Interfaz simplificada

El patrón Facade proporciona una interfaz simplificada para un subsistema complejo, haciendo que sea más fácil de usar y entender.

Implementación básica

# Subsistema complejo
class SubsistemaBaseDatos:
    def conectar(self):
        return "Conectado a base de datos"

    def ejecutar_query(self, query):
        return f"Ejecutando: {query}"

    def desconectar(self):
        return "Desconectado de base de datos"

class SubsistemaCache:
    def obtener(self, clave):
        return f"Obteniendo {clave} del caché"

    def guardar(self, clave, valor):
        return f"Guardando {clave}: {valor} en caché"

class SubsistemaLogger:
    def log(self, mensaje):
        return f"LOG: {mensaje}"

# Facade que simplifica el uso del subsistema
class FacadeServicioDatos:
    def __init__(self):
        self.db = SubsistemaBaseDatos()
        self.cache = SubsistemaCache()
        self.logger = SubsistemaLogger()

    def obtener_datos_usuario(self, user_id):
        # Interfaz simplificada que oculta la complejidad
        self.logger.log(f"Obteniendo datos para usuario {user_id}")

        # Intentar obtener del caché primero
        datos_cache = self.cache.obtener(f"user_{user_id}")
        if "Usuario encontrado" in datos_cache:
            return datos_cache

        # Si no está en caché, obtener de BD
        self.db.conectar()
        query = f"SELECT * FROM usuarios WHERE id = {user_id}"
        datos_db = self.db.ejecutar_query(query)
        self.db.desconectar()

        # Guardar en caché para futuras consultas
        self.cache.guardar(f"user_{user_id}", datos_db)

        return datos_db

# Uso del facade
servicio = FacadeServicioDatos()

# Interfaz simple en lugar de manejar múltiples subsistemas
datos = servicio.obtener_datos_usuario(123)
print(datos)

Facade para compilador

class AnalizadorLexico:
    def analizar(self, codigo):
        return f"Tokens extraídos de: {codigo[:20]}..."

class AnalizadorSintactico:
    def analizar(self, tokens):
        return f"Árbol sintáctico generado para: {tokens[:20]}..."

class GeneradorCodigo:
    def generar(self, arbol):
        return f"Código objeto generado para: {arbol[:20]}..."

class Optimizador:
    def optimizar(self, codigo):
        return f"Código optimizado: {codigo[:20]}..."

class FacadeCompilador:
    def __init__(self):
        self.lexer = AnalizadorLexico()
        self.parser = AnalizadorSintactico()
        self.generator = GeneradorCodigo()
        self.optimizer = Optimizador()

    def compilar(self, codigo_fuente, optimizar=True):
        print("Iniciando compilación...")

        tokens = self.lexer.analizar(codigo_fuente)
        print(f"✓ Análisis léxico completado")

        arbol = self.parser.analizar(tokens)
        print(f"✓ Análisis sintáctico completado")

        codigo = self.generator.generar(arbol)
        print(f"✓ Generación de código completado")

        if optimizar:
            codigo = self.optimizer.optimizar(codigo)
            print(f"✓ Optimización completada")

        print("Compilación finalizada")
        return codigo

# Uso
compilador = FacadeCompilador()
codigo = "def suma(a, b): return a + b"
resultado = compilador.compilar(codigo)
print(f"Resultado: {resultado}")

Paso 5: Proxy - Control de acceso

El patrón Proxy proporciona un sustituto o intermediario para controlar el acceso a un objeto, añadiendo lógica adicional como caching, logging, o control de acceso.

Proxy virtual (lazy loading)

from abc import ABC, abstractmethod

class Imagen(ABC):
    @abstractmethod
    def mostrar(self):
        pass

class ImagenReal(Imagen):
    def __init__(self, nombre_archivo):
        self.nombre_archivo = nombre_archivo
        self._cargar_imagen_desde_disco()

    def _cargar_imagen_desde_disco(self):
        print(f"Cargando imagen {self.nombre_archivo} desde disco...")
        # Simular carga costosa
        import time
        time.sleep(1)
        print(f"Imagen {self.nombre_archivo} cargada")

    def mostrar(self):
        print(f"Mostrando imagen {self.nombre_archivo}")

class ProxyImagen(Imagen):
    def __init__(self, nombre_archivo):
        self.nombre_archivo = nombre_archivo
        self._imagen_real = None

    def mostrar(self):
        if self._imagen_real is None:
            self._imagen_real = ImagenReal(self.nombre_archivo)
        self._imagen_real.mostrar()

# Uso
imagen1 = ProxyImagen("foto1.jpg")
imagen2 = ProxyImagen("foto2.jpg")

print("Imagen 1 creada (no cargada aún)")
print("Imagen 2 creada (no cargada aún)")

imagen1.mostrar()  # Carga y muestra
imagen1.mostrar()  # Solo muestra (ya cargada)
imagen2.mostrar()  # Carga y muestra

Proxy de protección

class ServicioBanco:
    def __init__(self, saldo_inicial=1000):
        self.saldo = saldo_inicial

    def retirar_dinero(self, cantidad, usuario):
        if cantidad <= self.saldo:
            self.saldo -= cantidad
            return f"Retirados ${cantidad}. Saldo restante: ${self.saldo}"
        return "Saldo insuficiente"

    def consultar_saldo(self, usuario):
        return f"Saldo actual: ${self.saldo}"

class ProxyBanco:
    def __init__(self, servicio_banco, usuario_autorizado):
        self.servicio_banco = servicio_banco
        self.usuario_autorizado = usuario_autorizado

    def retirar_dinero(self, cantidad, usuario):
        if self._verificar_acceso(usuario):
            return self.servicio_banco.retirar_dinero(cantidad, usuario)
        return "Acceso denegado"

    def consultar_saldo(self, usuario):
        if self._verificar_acceso(usuario):
            return self.servicio_banco.consultar_saldo(usuario)
        return "Acceso denegado"

    def _verificar_acceso(self, usuario):
        return usuario == self.usuario_autorizado

# Uso
banco_real = ServicioBanco()
proxy_banco = ProxyBanco(banco_real, "usuario123")

# Acceso autorizado
print(proxy_banco.consultar_saldo("usuario123"))
print(proxy_banco.retirar_dinero(200, "usuario123"))

# Acceso no autorizado
print(proxy_banco.consultar_saldo("usuario456"))
print(proxy_banco.retirar_dinero(100, "usuario456"))

Proxy de caching

class ServicioWeb:
    def obtener_datos(self, url):
        print(f"Haciendo petición HTTP a {url}")
        # Simular petición costosa
        import time
        time.sleep(0.5)
        return f"Datos de {url}"

class ProxyCacheWeb:
    def __init__(self, servicio_web):
        self.servicio_web = servicio_web
        self.cache = {}

    def obtener_datos(self, url):
        if url in self.cache:
            print(f"Obteniendo {url} del caché")
            return self.cache[url]

        datos = self.servicio_web.obtener_datos(url)
        self.cache[url] = datos
        return datos

# Uso
servicio = ServicioWeb()
proxy = ProxyCacheWeb(servicio)

# Primera petición - va al servicio real
print(proxy.obtener_datos("https://api.example.com/users"))
print()

# Segunda petición - usa caché
print(proxy.obtener_datos("https://api.example.com/users"))

Paso 6: Bridge - Separar abstracción e implementación

El patrón Bridge separa una abstracción de su implementación, permitiendo que ambas varíen independientemente.

Implementación básica

from abc import ABC, abstractmethod

# Implementación
class ImplementacionDibujo(ABC):
    @abstractmethod
    def dibujar_linea(self, x1, y1, x2, y2):
        pass

    @abstractmethod
    def dibujar_circulo(self, x, y, radio):
        pass

class DibujoVectorial(ImplementacionDibujo):
    def dibujar_linea(self, x1, y1, x2, y2):
        return f"Dibujando línea vectorial: ({x1},{y1}) -> ({x2},{y2})"

    def dibujar_circulo(self, x, y, radio):
        return f"Dibujando círculo vectorial: centro=({x},{y}), radio={radio}"

class DibujoRaster(ImplementacionDibujo):
    def dibujar_linea(self, x1, y1, x2, y2):
        return f"Dibujando línea raster: ({x1},{y1}) -> ({x2},{y2})"

    def dibujar_circulo(self, x, y, radio):
        return f"Dibujando círculo raster: centro=({x},{y}), radio={radio}"

# Abstracción
class Forma(ABC):
    def __init__(self, implementacion_dibujo):
        self.implementacion_dibujo = implementacion_dibujo

    @abstractmethod
    def dibujar(self):
        pass

# Abstracciones refinadas
class Circulo(Forma):
    def __init__(self, implementacion_dibujo, x, y, radio):
        super().__init__(implementacion_dibujo)
        self.x = x
        self.y = y
        self.radio = radio

    def dibujar(self):
        return self.implementacion_dibujo.dibujar_circulo(self.x, self.y, self.radio)

class Rectangulo(Forma):
    def __init__(self, implementacion_dibujo, x1, y1, x2, y2):
        super().__init__(implementacion_dibujo)
        self.x1 = x1
        self.y1 = y1
        self.x2 = x2
        self.y2 = y2

    def dibujar(self):
        linea1 = self.implementacion_dibujo.dibujar_linea(self.x1, self.y1, self.x2, self.y1)
        linea2 = self.implementacion_dibujo.dibujar_linea(self.x2, self.y1, self.x2, self.y2)
        linea3 = self.implementacion_dibujo.dibujar_linea(self.x2, self.y2, self.x1, self.y2)
        linea4 = self.implementacion_dibujo.dibujar_linea(self.x1, self.y2, self.x1, self.y1)
        return f"Rectángulo:\n{linea1}\n{linea2}\n{linea3}\n{linea4}"

# Uso
vectorial = DibujoVectorial()
raster = DibujoRaster()

circulo_vectorial = Circulo(vectorial, 10, 10, 5)
rectangulo_raster = Rectangulo(raster, 0, 0, 20, 10)

print(circulo_vectorial.dibujar())
print()
print(rectangulo_raster.dibujar())

Paso 7: Composite - Estructuras de árbol

El patrón Composite permite tratar objetos individuales y composiciones de objetos de manera uniforme, creando estructuras de árbol.

Implementación básica

from abc import ABC, abstractmethod

class ComponenteGrafico(ABC):
    @abstractmethod
    def dibujar(self):
        pass

    @abstractmethod
    def mover(self, x, y):
        pass

class Forma(ComponenteGrafico):
    def __init__(self, nombre):
        self.nombre = nombre
        self.x = 0
        self.y = 0

    def dibujar(self):
        return f"Dibujando {self.nombre} en ({self.x}, {self.y})"

    def mover(self, x, y):
        self.x = x
        self.y = y
        return f"Moviendo {self.nombre} a ({x}, {y})"

class GrupoGrafico(ComponenteGrafico):
    def __init__(self, nombre):
        self.nombre = nombre
        self.componentes = []
        self.x = 0
        self.y = 0

    def agregar(self, componente):
        self.componentes.append(componente)

    def remover(self, componente):
        self.componentes.remove(componente)

    def dibujar(self):
        resultado = [f"Dibujando grupo '{self.nombre}':"]
        for componente in self.componentes:
            resultado.append(f"  {componente.dibujar()}")
        return "\n".join(resultado)

    def mover(self, x, y):
        self.x = x
        self.y = y
        resultado = [f"Moviendo grupo '{self.nombre}' a ({x}, {y}):"]
        for componente in self.componentes:
            # Mover relativamente
            resultado.append(f"  {componente.mover(componente.x + x, componente.y + y)}")
        return "\n".join(resultado)

# Uso
# Crear formas individuales
circulo = Forma("Círculo")
cuadrado = Forma("Cuadrado")
triangulo = Forma("Triángulo")

# Crear grupos
grupo_formas = GrupoGrafico("Formas Básicas")
grupo_formas.agregar(circulo)
grupo_formas.agregar(cuadrado)

grupo_completo = GrupoGrafico("Dibujo Completo")
grupo_completo.agregar(grupo_formas)
grupo_completo.agregar(triangulo)

print(grupo_completo.dibujar())
print()
print(grupo_completo.mover(10, 20))

Composite para sistema de archivos

from abc import ABC, abstractmethod

class ElementoSistemaArchivos(ABC):
    def __init__(self, nombre):
        self.nombre = nombre

    @abstractmethod
    def obtener_tamano(self):
        pass

    @abstractmethod
    def mostrar(self, indentacion=0):
        pass

class Archivo(ElementoSistemaArchivos):
    def __init__(self, nombre, tamano):
        super().__init__(nombre)
        self.tamano = tamano

    def obtener_tamano(self):
        return self.tamano

    def mostrar(self, indentacion=0):
        return "  " * indentacion + f"📄 {self.nombre} ({self.tamano} KB)"

class Directorio(ElementoSistemaArchivos):
    def __init__(self, nombre):
        super().__init__(nombre)
        self.elementos = []

    def agregar(self, elemento):
        self.elementos.append(elemento)

    def remover(self, elemento):
        self.elementos.remove(elemento)

    def obtener_tamano(self):
        return sum(elemento.obtener_tamano() for elemento in self.elementos)

    def mostrar(self, indentacion=0):
        resultado = ["  " * indentacion + f"📁 {self.nombre}/"]
        for elemento in self.elementos:
            resultado.append(elemento.mostrar(indentacion + 1))
        return "\n".join(resultado)

# Uso
# Crear archivos
archivo1 = Archivo("documento.txt", 5)
archivo2 = Archivo("imagen.jpg", 1200)
archivo3 = Archivo("codigo.py", 15)

# Crear directorios
directorio_docs = Directorio("Documentos")
directorio_docs.agregar(archivo1)

directorio_imagenes = Directorio("Imágenes")
directorio_imagenes.agregar(archivo2)

directorio_proyecto = Directorio("Proyecto")
directorio_proyecto.agregar(archivo3)
directorio_proyecto.agregar(directorio_docs)
directorio_proyecto.agregar(directorio_imagenes)

# Mostrar estructura
print(directorio_proyecto.mostrar())
print(f"\nTamaño total del proyecto: {directorio_proyecto.obtener_tamano()} KB")

Paso 8: Flyweight - Compartir objetos eficientemente

El patrón Flyweight permite compartir objetos eficientemente, reduciendo el uso de memoria cuando tienes muchos objetos similares.

Implementación básica

class TipoCaracter:
    def __init__(self, fuente, tamano, color):
        self.fuente = fuente
        self.tamano = tamano
        self.color = color

    def mostrar(self, posicion):
        return f"Caracter en fuente {self.fuente}, tamaño {self.tamano}, color {self.color} en posición {posicion}"

class FabricaCaracteresFlyweight:
    def __init__(self):
        self.caracteres = {}

    def obtener_caracter(self, fuente, tamano, color):
        clave = (fuente, tamano, color)

        if clave not in self.caracteres:
            self.caracteres[clave] = TipoCaracter(fuente, tamano, color)
            print(f"Creando nuevo tipo de caracter: {clave}")
        else:
            print(f"Reutilizando tipo de caracter: {clave}")

        return self.caracteres[clave]

class Caracter:
    def __init__(self, tipo_caracter, posicion):
        self.tipo_caracter = tipo_caracter
        self.posicion = posicion

    def mostrar(self):
        return self.tipo_caracter.mostrar(self.posicion)

# Uso
fabrica = FabricaCaracteresFlyweight()

# Crear muchos caracteres con pocos tipos compartidos
caracteres = []
for i in range(100):
    # Solo 3 tipos diferentes de caracteres
    if i % 3 == 0:
        tipo = fabrica.obtener_caracter("Arial", 12, "Negro")
    elif i % 3 == 1:
        tipo = fabrica.obtener_caracter("Times", 14, "Azul")
    else:
        tipo = fabrica.obtener_caracter("Courier", 10, "Rojo")

    caracter = Caracter(tipo, i)
    caracteres.append(caracter)

print(f"\nTotal de caracteres: {len(caracteres)}")
print(f"Total de tipos de caracter: {len(fabrica.caracteres)}")
print(f"Memoria ahorrada: {len(caracteres) - len(fabrica.caracteres)} objetos")

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

Cuándo usar cada patrón estructural

Patrón Cuándo usarlo Ventajas Desventajas
Adapter Interfaces incompatibles, legacy code Reutilización, no modifica código existente Añade complejidad
Decorator Funcionalidad dinámica, herencia múltiple Flexible, composición Puede crear muchas clases pequeñas
Facade Subsistemas complejos Simplifica uso Puede ocultar funcionalidad necesaria
Proxy Control de acceso, lazy loading Transparente, controla acceso Puede añadir latencia
Bridge Abstracción e implementación variables Independencia, extensible Aumenta complejidad
Composite Estructuras jerárquicas Uniformidad, extensible Puede ser overkill para estructuras simples
Flyweight Muchos objetos similares Ahorra memoria Aumenta complejidad de código

Paso 10: Proyecto práctico - Sistema de archivos

Vamos a crear un sistema de archivos completo que utilice múltiples patrones estructurales.

from abc import ABC, abstractmethod
import os
from datetime import datetime

# Flyweight para tipos de archivo
class TipoArchivoFlyweight:
    def __init__(self, extension, descripcion, icono):
        self.extension = extension
        self.descripcion = descripcion
        self.icono = icono

    def obtener_info(self):
        return f"{self.icono} {self.descripcion} ({self.extension})"

class FabricaTiposArchivo:
    _instance = None
    _tipos = {}

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._inicializar_tipos()
        return cls._instance

    @classmethod
    def _inicializar_tipos(cls):
        cls._tipos = {
            'txt': TipoArchivoFlyweight('txt', 'Archivo de texto', '📄'),
            'py': TipoArchivoFlyweight('py', 'Script Python', '🐍'),
            'jpg': TipoArchivoFlyweight('jpg', 'Imagen JPEG', '🖼️'),
            'pdf': TipoArchivoFlyweight('pdf', 'Documento PDF', '📕'),
            'mp3': TipoArchivoFlyweight('mp3', 'Archivo de audio', '🎵'),
        }

    def obtener_tipo(self, extension):
        return self._tipos.get(extension.lower(), 
                              TipoArchivoFlyweight(extension, f'Archivo {extension.upper()}', '📄'))

# Composite para estructura de archivos
class ElementoSistema(ABC):
    def __init__(self, nombre, ruta_padre=""):
        self.nombre = nombre
        self.ruta_padre = ruta_padre
        self.fecha_creacion = datetime.now()
        self.fecha_modificacion = datetime.now()

    @property
    def ruta_completa(self):
        if self.ruta_padre:
            return os.path.join(self.ruta_padre, self.nombre)
        return self.nombre

    @abstractmethod
    def obtener_tamano(self):
        pass

    @abstractmethod
    def mostrar(self, indentacion=0):
        pass

class Archivo(ElementoSistema):
    def __init__(self, nombre, tamano=0, ruta_padre=""):
        super().__init__(nombre, ruta_padre)
        self.tamano = tamano
        self.extension = os.path.splitext(nombre)[1][1:] if '.' in nombre else ''
        self.tipo = FabricaTiposArchivo().obtener_tipo(self.extension)

    def obtener_tamano(self):
        return self.tamano

    def mostrar(self, indentacion=0):
        espacio = "  " * indentacion
        info_tipo = self.tipo.obtener_info()
        return f"{espacio}{info_tipo} {self.nombre} ({self.tamano} KB)"

class Directorio(ElementoSistema):
    def __init__(self, nombre, ruta_padre=""):
        super().__init__(nombre, ruta_padre)
        self.elementos = []

    def agregar(self, elemento):
        elemento.ruta_padre = self.ruta_completa
        self.elementos.append(elemento)
        self.fecha_modificacion = datetime.now()

    def remover(self, elemento):
        if elemento in self.elementos:
            self.elementos.remove(elemento)
            self.fecha_modificacion = datetime.now()

    def obtener_tamano(self):
        return sum(elemento.obtener_tamano() for elemento in self.elementos)

    def mostrar(self, indentacion=0):
        espacio = "  " * indentacion
        resultado = [f"{espacio}📁 {self.nombre}/"]

        for elemento in sorted(self.elementos, key=lambda x: (isinstance(x, Directorio), x.nombre)):
            resultado.append(elemento.mostrar(indentacion + 1))

        return "\n".join(resultado)

# Adapter para sistema de archivos real
class AdaptadorSistemaArchivos:
    def __init__(self, ruta_base):
        self.ruta_base = ruta_base

    def cargar_directorio(self, ruta_relativa=""):
        ruta_completa = os.path.join(self.ruta_base, ruta_relativa)

        if not os.path.exists(ruta_completa):
            raise FileNotFoundError(f"Directorio no encontrado: {ruta_completa}")

        directorio = Directorio(os.path.basename(ruta_completa) or "raiz", 
                               os.path.dirname(ruta_completa) if ruta_relativa else "")

        for item in os.listdir(ruta_completa):
            ruta_item = os.path.join(ruta_completa, item)

            if os.path.isdir(ruta_item):
                subdirectorio = self.cargar_directorio(os.path.join(ruta_relativa, item))
                directorio.agregar(subdirectorio)
            else:
                tamano = os.path.getsize(ruta_item) // 1024  # KB
                archivo = Archivo(item, tamano, ruta_completa)
                directorio.agregar(archivo)

        return directorio

# Facade para operaciones comunes
class FacadeSistemaArchivos:
    def __init__(self, ruta_base):
        self.ruta_base = ruta_base
        self.adaptador = AdaptadorSistemaArchivos(ruta_base)
        self.raiz = None

    def cargar_sistema(self):
        self.raiz = self.adaptador.cargar_directorio()
        return self.raiz

    def buscar_archivos(self, extension, directorio=None):
        if directorio is None:
            directorio = self.raiz

        encontrados = []

        for elemento in directorio.elementos:
            if isinstance(elemento, Archivo) and elemento.extension == extension:
                encontrados.append(elemento)
            elif isinstance(elemento, Directorio):
                encontrados.extend(self.buscar_archivos(extension, elemento))

        return encontrados

    def obtener_estadisticas(self, directorio=None):
        if directorio is None:
            directorio = self.raiz

        total_archivos = 0
        total_directorios = 0
        tamano_total = 0
        tipos_archivo = {}

        def contar(elemento):
            nonlocal total_archivos, total_directorios, tamano_total

            if isinstance(elemento, Archivo):
                total_archivos += 1
                tamano_total += elemento.tamano
                tipos_archivo[elemento.extension] = tipos_archivo.get(elemento.extension, 0) + 1
            elif isinstance(elemento, Directorio):
                total_directorios += 1
                for subelemento in elemento.elementos:
                    contar(subelemento)

        contar(directorio)

        return {
            'archivos': total_archivos,
            'directorios': total_directorios,
            'tamano_total': tamano_total,
            'tipos_archivo': tipos_archivo
        }

# Demo del sistema
def demo_sistema_archivos():
    # Crear estructura de ejemplo
    raiz = Directorio("MiProyecto")

    # Crear subdirectorios
    src = Directorio("src")
    docs = Directorio("docs")
    assets = Directorio("assets")

    raiz.agregar(src)
    raiz.agregar(docs)
    raiz.agregar(assets)

    # Agregar archivos
    src.agregar(Archivo("main.py", 15))
    src.agregar(Archivo("utils.py", 8))
    src.agregar(Archivo("config.py", 5))

    docs.agregar(Archivo("README.md", 3))
    docs.agregar(Archivo("manual.pdf", 250))

    assets.agregar(Archivo("logo.jpg", 1200))
    assets.agregar(Archivo("icon.png", 45))
    assets.agregar(Archivo("background.mp3", 3500))

    # Usar facade
    facade = FacadeSistemaArchivos("")
    facade.raiz = raiz

    print("=== Estructura del Sistema de Archivos ===")
    print(raiz.mostrar())

    print("\n=== Estadísticas ===")
    stats = facade.obtener_estadisticas()
    print(f"Archivos: {stats['archivos']}")
    print(f"Directorios: {stats['directorios']}")
    print(f"Tamaño total: {stats['tamano_total']} KB")
    print(f"Tipos de archivo: {stats['tipos_archivo']}")

    print("\n=== Búsqueda de archivos Python ===")
    archivos_py = facade.buscar_archivos('py')
    for archivo in archivos_py:
        print(f"  {archivo.ruta_completa}")

if __name__ == "__main__":
    demo_sistema_archivos()

Conclusión

¡Has dominado los patrones de diseño estructurales! Estos patrones te permiten organizar y simplificar la estructura de tus aplicaciones, haciendo que el código sea más mantenible y flexible.

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 estructurales avanzados, visita nuestra sección de tutoriales.


¡Sigue practicando y aplicando estos patrones en proyectos reales!


💡 Tip Importante

📝 Mejores Prácticas en Patrones Estructurales

  • Elige el patrón adecuado: Analiza la estructura del problema 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
  • Mantén la simplicidad: No uses patrones complejos para problemas simples
  • Documenta tus decisiones: Explica por qué elegiste ciertos patrones en tu código
  • Prueba tus estructuras: Asegúrate de que tus patrones funcionen correctamente
  • Considera el rendimiento: Algunos patrones pueden afectar el rendimiento

📚 Recursos Recomendados:

¡Estos patrones te ayudarán a crear software con mejor estructura y organizació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: Fundamentos de Programación: Patrones de Diseño de Comportamiento
Patrones de Diseño

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.

José Elías Romero Guanipa
02 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 - 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