Python Patrón Composite
El patrón Composite es un patrón de diseño estructural que te permite combinar objetos en estructuras de árbol para representar jerarquías de "parte-todo". El patrón Composite permite al cliente manejar objetos individuales y composiciones de objetos de manera uniforme, sin necesidad de preocuparse si está tratando con un objeto individual o con toda la estructura compuesta.
Analogía de la vida cotidiana
Imagina la estructura organizativa de un sistema de archivos:
- Archivo individual: Es la unidad más básica
- Carpeta: Puede contener múltiples archivos u otras carpetas
- Ya sea que operes con un archivo o una carpeta, puedes realizar acciones como "abrir", "eliminar", "renombrar", etc.
Esta relación jerárquica de "parte-todo" es el escenario de aplicación típico del patrón Composite.
Componentes centrales del patrón Composite
1. Interfaz de componente (Component)
Este es el núcleo del patrón Composite; define la interfaz común para todos los objetos (incluyendo nodos hoja y nodos compuestos).
Ejemplo
from typing import List
class FileSystemComponent(ABC):
"""Clase base abstracta del componente del sistema de archivos"""
def __init__(self, name: str):
self.name = name
self.parent = None
@abstractmethod
def display(self, indent: int = 0) -> None:
"""Muestra la información del componente"""
pass
@abstractmethod
def get_size(self) -> int:
"""Obtiene el tamaño del componente"""
pass
def get_path(self) -> str:
"""Obtiene la ruta completa"""
if self.parent:
return f"{self.parent.get_path()}/{self.name}"
return self.name
2. Nodo hoja (Leaf)
Representa los objetos hoja en la composición; los nodos hoja no tienen hijos.
Ejemplo
"""Clase de archivo - Nodo hoja"""
def __init__(self, name: str, size: int):
super().__init__(name)
self._size = size
def display(self, indent: int = 0) -> None:
"""Muestra la información del archivo"""
spaces = " " * indent
print(f"{spaces}📄 {self.name} ({self._size} bytes)")
def get_size(self) -> int:
"""Devuelve el tamaño del archivo"""
return self._size
3. Nodo compuesto (Composite)
Representa objetos compuestos que pueden contener subcomponentes; define métodos para almacenar subcomponentes.
Ejemplo
"""Clase de directorio - Nodo compuesto"""
def __init__(self, name: str):
super().__init__(name)
self._children: List[FileSystemComponent] = []
def add(self, component: FileSystemComponent) -> None:
"""Añadir subcomponente"""
component.parent = self
self._children.append(component)
def remove(self, component: FileSystemComponent) -> None:
"""Eliminar subcomponente"""
self._children.remove(component)
component.parent = None
def display(self, indent: int = 0) -> None:
"""Muestra el directorio y todos sus subcomponentes"""
spaces = " " * indent
print(f"{spaces}📁 {self.name}/")
# Mostrar recursivamente todos los subcomponentes
for child in self._children:
child.display(indent + 1)
def get_size(self) -> int:
"""Calcula el tamaño total del directorio (incluyendo todos los subcomponentes)"""
total_size = 0
for child in self._children:
total_size += child.get_size()
return total_size
def find_component(self, name: str) -> FileSystemComponent:
"""Busca un componente por nombre especificado"""
for child in self._children:
if child.name == name:
return child
if isinstance(child, Directory):
found = child.find_component(name)
if found:
return found
return None
Ejemplo completo: Simulación de sistema de archivos
Veamos un ejemplo completo para demostrar la aplicación práctica del patrón Composite:
Ejemplo
"""Demuestra el uso del patrón Composite"""
# Crear directorio raíz
root = Directory("root")
# Crear subdirectorios
documents = Directory("documents")
pictures = Directory("pictures")
music = Directory("music")
# Crear archivos
readme = File("README.txt", 1024)
notes = File("notes.md", 2048)
photo1 = File("vacation.jpg", 1536000)
photo2 = File("family.png", 2048000)
song1 = File("song1.mp3", 4096000)
song2 = File("song2.mp3", 5120000)
# Construir la estructura de directorios
root.add(readme)
root.add(documents)
root.add(pictures)
root.add(music)
documents.add(notes)
pictures.add(photo1)
pictures.add(photo2)
music.add(song1)
music.add(song2)
# Mostrar toda la estructura del sistema de archivos
print("=== Estructura del sistema de archivos ===")
root.display()
print("\n"=== Estadísticas de tamaño ===")
print(f"Tamaño total del directorio raíz: {root.get_size()} bytes")
print(f"Tamaño del directorio de imágenes: {pictures.get_size()} bytes")
print(f"Tamaño del directorio de música: {music.get_size()} bytes")
print("\n"=== Información de ruta ===")
print(f"Ruta del archivo: {photo1.get_path()}")
print(f"Ruta del directorio: {pictures.get_path()}")
print("\n"=== Buscar componente ===")
found = root.find_component("song1.mp3")
if found:
print(f"Archivo encontrado: {found.get_path()}")
# Ejecutar demostración
if __name__ == "__main__":
demonstrate_composite_pattern()
Al ejecutar el código anterior, verás la siguiente salida:

Diagrama de clases UML del patrón Composite

Ventajas y desventajas del patrón Composite
Ventajas
- Interfaz unificada: El cliente puede usar objetos individuales y objetos compuestos de manera consistente
- Principio de abierto/cerrado: Es fácil añadir nuevos tipos de componentes sin modificar el código existente
- Simplifica el código del cliente: El cliente no necesita determinar si está tratando con un objeto individual o una composición
- Estructura jerárquica flexible: Puede construir estructuras de árbol complejas
Desventajas
- Complejidad de diseño: Un diseño demasiado generalizado puede hacer que el sistema sea complejo
- Problemas de seguridad de tipos: En algunos casos, puede ser necesaria la verificación de tipos en tiempo de ejecución
- Consideraciones de rendimiento: Para estructuras jerárquicas grandes, las operaciones recursivas pueden afectar el rendimiento
Escenarios de aplicación práctica
1. Sistema de componentes GUI
Ejemplo
"""Clase base de componente UI"""
def render(self):
pass
def add(self, component):
pass
class Button(UIComponent):
"""Botón - Nodo hoja"""
def render(self):
print("Renderizar botón")
class Panel(UIComponent):
"""Panel - Nodo compuesto"""
def __init__(self):
self.children = []
def add(self, component):
self.children.append(component)
def render(self):
print("Comenzar a renderizar el panel")
for child in self.children:
child.render()
print("Finalizar renderizado del panel")
2. Gestión de estructura organizativa
Ejemplo
"""Clase base de empleado"""
def get_salary(self):
pass
class Developer(Employee):
"""Desarrollador - Nodo hoja"""
def __init__(self, salary):
self.salary = salary
def get_salary(self):
return self.salary
class Department(Employee):
"""Departamento - Nodo compuesto"""
def __init__(self):
self.employees = []
def add(self, employee):
self.employees.append(employee)
def get_salary(self):
return sum(emp.get_salary() for emp in self.employees)
Ejercicios prácticos
Ejercicio 1: Extender el sistema de archivos
Añade las siguientes funcionalidades al sistema de archivos:
- Implementar la función de copia de archivos
- Añadir búsqueda con filtro por tipo de archivo
- Implementar visualización con límite de profundidad de directorio
Ejercicio 2: Diseño de sistema de menús
Diseña un sistema de menú de restaurante, con los siguientes requisitos:
- El menú puede contener platos o submenús
- Poder calcular el precio total
- Soportar el filtrado de platos por categoría
Ejercicio 3: Organigrama
Crea un sistema de estructura organizativa de una empresa:
- Los empleados pueden ser individuos o departamentos
- Un departamento puede contener otros empleados o subdepartamentos
- Poder calcular el número total de personas y el salario total del departamento
Resumen
El patrón Composite maneja objetos individuales y composiciones de objetos mediante una interfaz unificada, lo que hace que el código del cliente sea más conciso y flexible. Este patrón es especialmente adecuado para escenarios con estructuras jerárquicas, como sistemas de archivos, componentes GUI, estructuras organizativas, etc.
Puntos clave:
- La interfaz de componente define las operaciones comunes de todos los objetos
- Los nodos hoja implementan funcionalidades básicas
- Los nodos compuestos gestionan subcomponentes y proporcionan operaciones agregadas
- El cliente no necesita distinguir entre objetos individuales y composiciones de objetos