Problema
Tratar objetos individuales y composiciones de objetos de manera uniforme en estructuras jerárquicas.
Propósito
Permite que objetos individuales (Leaf) y contenedores (Composite) implementen la misma interfaz, de modo que las operaciones se ejecuten recursivamente por toda la estructura sin que el cliente sepa si está tratando con un elemento simple o compuesto.
Concepto clave
Tratar lo simple y lo complejo igual: Un archivo y una carpeta implementan la misma interfaz. Cuando pides el tamaño de una carpeta, automáticamente suma el tamaño de todo su contenido recursivamente.
Casos de uso comunes
- Sistemas de archivos: archivos y carpetas
- UI: botones individuales y paneles que contienen otros componentes
- Menús: elementos de menú y submenús
- Organizaciones: empleados y departamentos
- Validación: reglas simples y grupos de reglas
¿Quién es quién en Composite?
Actor | Lo que realmente es | Ejemplo | Analogía |
---|---|---|---|
Component | Interfaz común para simples y compuestos | FileSystemComponent - operaciones para archivos y carpetas |
“Elemento del sistema de archivos” |
Leaf | Objetos simples, no tienen hijos | File - hace el trabajo real, no contiene otros |
Archivo (no contiene nada más) |
Composite | Contenedores, delegan recursivamente | Directory - contiene otros componentes |
Carpeta (contiene archivos y carpetas) |
Client | Usa la estructura, trata todo igual | Usuario - pide tamaño sin saber si es archivo o carpeta | Usuario (transparencia total) |
Diagrama
classDiagram
namespace CompositePattern {
class Component {
<<interface>>
+operation()
+add(Component)
+remove(Component)
+getChild(int) Component
}
class Leaf {
+operation()
}
class Composite {
-children: List~Component~
+operation()
+add(Component)
+remove(Component)
+getChild(int) Component
}
class Client {
+clientCode(Component)
}
}
Component <|.. Leaf
Component <|.. Composite
Composite --> Component
Client --> Component
Ejemplo práctico
classDiagram
namespace FileSystemExample {
class FileSystemComponent {
<<interface>>
+getName() String
+getSize() long
+display(indent)
}
class File {
-name: String
-size: long
+getName() String
+getSize() long
+display(indent)
}
class Directory {
-name: String
-children: List~FileSystemComponent~
+getName() String
+getSize() long
+display(indent)
+add(FileSystemComponent)
+remove(FileSystemComponent)
}
}
FileSystemComponent <|.. File
FileSystemComponent <|.. Directory
Directory --> FileSystemComponent
Ejemplo de recursión
📁 root/ (25KB total)
├── 📄 README.md (7KB) ← Leaf: devuelve 7KB
├── 📁 documents/ (13KB) ← Composite: suma sus hijos
│ ├── 📄 config.json (8KB) ← Leaf: devuelve 8KB
│ └── 📄 notes.txt (5KB) ← Leaf: devuelve 5KB
└── 📁 photos/ (5KB) ← Composite: suma sus hijos
└── 📄 vacation.jpg (5KB) ← Leaf: devuelve 5KB
Llamada: root.getSize()
→ 7 + documents.getSize() + photos.getSize() → 7 + (8+5) + (5) = 25KB
Ventajas
- Uniformidad: Trata objetos simples y complejos de la misma manera
- Flexibilidad: Fácil agregar nuevos tipos de componentes
- Recursión: Operaciones se propagan automáticamente por la estructura
- Transparencia: Cliente no necesita distinguir entre leaf y composite
Desventajas
- Generalización excesiva: Puede hacer el diseño demasiado general
- Restricciones: Difícil restringir qué componentes puede contener un composite
- Complejidad: Puede ser confuso si la jerarquía es muy profunda
Cuándo usar
- Quieres representar jerarquías parte-todo
- Quieres que clientes ignoren diferencias entre objetos individuales y composiciones
- La estructura puede ser representada como árbol
- Necesitas aplicar operaciones uniformemente en toda la estructura
Cuándo NO usar
- La estructura es plana (no jerárquica)
- Los objetos individuales y las composiciones tienen comportamientos muy diferentes
- La complejidad adicional no se justifica