Desarrollo

El patrón de diseño Composite

Publicado el
Etiquetas: patterns design patterns
El patrón de diseño Composite
Comparte

El patrón de diseño Composite se enmarca dentro de los patrones estructurales y nos proporciona una manera de crear estructuras jerárquicas de objetos, donde los objetos individuales y las composiciones de objetos se tratan de la misma manera.

Este patrón nos permite crear objetos complejos a partir de objetos más simples y representar cualquier nivel de jerarquía. Es decir, podemos tratar un objeto individual y una composición de objetos de la misma manera, haciendo que el uso de los mismos sea muy sencillo e intuitivo.

La idea principal detrás del patrón Composite es que los objetos que componen una estructura jerárquica deben ser tratados de la misma manera, sin importar si se trata de un objeto único o de una composición de objetos. Así, podemos tratar un objeto individual y una composición de objetos de la misma manera, haciendo que el uso de los mismos sea muy sencillo e intuitivo.

La estructura del patrón Composite se compone de dos elementos: el Component y el Composite. El Component es la interfaz que define los métodos que todos los objetos en la estructura deben implementar. Por otro lado, el Composite es la clase que implementa el Component y que puede contener otros objetos del mismo tipo, permitiendo así construir estructuras jerárquicas.

Diagrama

El patrón de diseño Composite se puede representar del siguiente modo:

                                      Component
                                          +
                                          |
                         +----------------+-------------+
                         |                              |
                         |                              |
                         |                              |
                      Leaf                           Composite
                         +                              +
                         |                              |
                         |                              |
                         |                              |
              (no tiene hijos)                 +--------+--------+
                                                |                 |
                                                |                 |
                                                |                 |
                                             Leaf              Composite
                                                +                 +
                                                |                 |
                                                |                 |
                                                |                 |
                                         (no tiene hijos)       +--------+--------+
                                                                         |        |
                                                                         |        |
                                                                         |        |
                                                                      Leaf     Leaf
                                                                         +        +
                                                                         |        |
                                                                         |        |
                                                                         |        |
                                                                  (no tiene hijos) (no tiene hijos)

En este diagrama, la clase Component es la clase base que define la interfaz para todos los objetos en la estructura, ya sean hojas o compuestos. La clase Leaf es una implementación concreta de Component que representa una hoja en la estructura. La clase Composite es otra implementación concreta de Component que representa un objeto compuesto que contiene hojas o subcompuestos.

La flecha que va desde Component a Leaf indica que Leaf es un tipo de Component. La flecha que va desde Component a Composite indica lo mismo para Composite. La flecha que va desde Composite a Component indica que un objeto compuesto puede contener uno o más componentes, ya sean hojas o subcompuestos.

Ejemplos

Java

Supongamos que necesitamos representar una estructura de una empresa, donde se tienen tanto empleados individuales como departamentos, y cada uno de ellos tiene un nombre y una descripción. Además, queremos poder calcular el salario total de toda la empresa sumando el salario de cada empleado individual y de cada departamento. Para modelar esta estructura y permitir este cálculo, podemos usar el patrón Composite.

En primer lugar, creamos una interfaz llamada Component que define la operación getSalary() que se encargará de calcular el salario total de la empresa y que será implementada por todas las clases que forman la estructura, tanto empleados individuales como departamentos:

public interface Component {
    public double getSalary();
}

Luego, creamos la clase Employee que representa a un empleado individual y que implementa la interfaz Component:

public class Employee implements Component {
    private String name;
    private String description;
    private double salary;

    public Employee(String name, String description, double salary) {
        this.name = name;
        this.description = description;
        this.salary = salary;
    }

    @Override
    public double getSalary() {
        return this.salary;
    }

    // Getters y setters
}

La clase Employee tiene tres atributos: name (nombre del empleado), description (descripción del empleado) y salary (salario del empleado). Además, implementa el método getSalary() que simplemente devuelve el salario del empleado.

A continuación, creamos la clase Department que representa a un departamento y que también implementa la interfaz Component:

public class Department implements Component {
    private String name;
    private String description;
    private List<Component> components;

    public Department(String name, String description) {
        this.name = name;
        this.description = description;
        this.components = new ArrayList<Component>();
    }

    @Override
    public double getSalary() {
        double totalSalary = 0;
        for (Component component : components) {
            totalSalary += component.getSalary();
        }
        return totalSalary;
    }

    public void add(Component component) {
        this.components.add(component);
    }

    public void remove(Component component) {
        this.components.remove(component);
    }

    // Getters y setters
}

La clase Department tiene tres atributos: name (nombre del departamento), description (descripción del departamento) y components (una lista de componentes que forman el departamento, que pueden ser tanto empleados individuales como otros departamentos). La clase implementa el método getSalary() que suma el salario de todos los componentes que forman el departamento. Además, la clase tiene dos métodos add() y remove() que permiten agregar y eliminar componentes de la lista components.

Por último, podemos crear una clase Client que use la estructura creada y realice el cálculo del salario total de la empresa:

public class Client {
    public static void main(String[] args) {
        Component john = new Employee("John", "Manager", 5000.0);
        Component mary = new Employee("Mary", "Developer", 4000.0);
        Component bob = new Employee("Bob", "Developer", 4000.0);

        Department development = new Department("Development", "Software development department");
        development.add(mary);
        development.add(bob);

        Department company = new Department("Company", "Overall company");
        company.add(john);
        company.add(development);

        System.out.println(company.getDetails());
    }
}

En este ejemplo, se ha creado una estructura jerárquica de objetos Component que representa una empresa, donde un Component puede ser un Employee o un Department. Un Department, a su vez, puede contener otros Department y/o Employee.

La clase Component es la clase abstracta base de la jerarquía, y tiene los métodos abstractos getDetails() y add(Component c). El método getDetails() es el encargado de devolver una cadena con los detalles del objeto, mientras que el método add() es el encargado de agregar un Component a la jerarquía.

Las clases Employee y Department son las hojas y los nodos del árbol, respectivamente. La clase Employee representa un empleado, y la clase Department representa un departamento. Ambas heredan de la clase Component y deben implementar sus métodos abstractos.

La clase Client es la clase encargada de crear la estructura jerárquica y mostrar los detalles del objeto raíz. En este caso, se ha creado una empresa y se han añadido un Manager, un departamento de desarrollo y dos desarrolladores. Finalmente, se muestra la información de la empresa llamando al método getDetails() del objeto raíz.

Python

from abc import ABC, abstractmethod

class Component(ABC):
    """
    La clase base Componente declara operaciones comunes tanto para simples como para complejos
    objetos de la estructura.
    """
    @abstractmethod
    def operation(self):
        pass

class Leaf(Component):
    """
    Las hojas representan objetos finales de la estructura. Una hoja no puede tener subobjetos.
    """
    def operation(self):
        # El código de la operación de una hoja debe ser el más simple, ya que
        # no tiene subobjetos que puedan hacer parte del trabajo por ella.
        return "Leaf"

class Composite(Component):
    """
    La clase Composite representa a los objetos complejos que pueden tener
    subobjetos. Los compuestos suelen delegar el trabajo real a sus subobjetos
    y luego "resumir" el resultado.
    """
    def __init__(self):
        self._children = []

    def add(self, component: Component) -> None:
        """
        Un objeto de componente complejo puede agregar o eliminar otros componentes
        (simples o complejos) como sus hijos.
        """
        self._children.append(component)

    def remove(self, component: Component) -> None:
        self._children.remove(component)

    def is_composite(self) -> bool:
        return True

    def operation(self):
        """
        El Composite lleva a cabo su trabajo principal delegando a sus hijos
        """
        results = []
        for child in self._children:
            results.append(child.operation())
        return f"Branch({'+'.join(results)})"

if __name__ == "__main__":
    # El cliente trabaja con todos los componentes a través de la interfaz base.
    def client_code(component: Component) -> None:
        print(f"RESULTADO: {component.operation()}", end="")

    # Crea una estructura de objetos simples y complejos
    tree = Composite()
    tree.add(Leaf())
    tree.add(Leaf())

    branch1 = Composite()
    branch1.add(Leaf())
    branch1.add(Leaf())
    tree.add(branch1)

    branch2 = Composite()
    branch2.add(Leaf())
    tree.add(branch2)

    # Ejecuta la operación en toda la estructura
    print("Cliente: Ejecuto la operacion en la estructura completa:")
    client_code(tree)
    print("\n\n")

    # Ejecuta la operación en una parte de la estructura
    print("Cliente: Ejecuto la operacion en una rama:")
    client_code(branch1)

Este ejemplo implementa un árbol compuesto que se compone de dos tipos de objetos: hojas y ramas. Las hojas representan objetos finales de la estructura, mientras que las ramas pueden tener otros objetos como hijos. La interfaz Componente define la operación operation() que es común a todos los componentes. La clase Leaf implementa esta operación de la manera más simple posible, mientras que la clase Composite delega el trabajo a sus hijos. El ejemplo también muestra cómo un cliente puede trabajar con una estructura de componentes complejos a través de la interfaz Componente genérica, y cómo se puede ejecutar la operación en toda la estructura o en una parte de la estructura.

Go

package main

import "fmt"

// Component interface
type Component interface {
    ShowDetails()
}

// Employee struct represents a leaf node
type Employee struct {
    name         string
    position     string
    salary       float64
}

// ShowDetails method for Employee
func (e *Employee) ShowDetails() {
    fmt.Printf("%s - %s: %.2f\n", e.name, e.position, e.salary)
}

// Department struct represents a composite node
type Department struct {
    name        string
    description string
    components  []Component
}

// Add method for Department
func (d *Department) Add(c Component) {
    d.components = append(d.components, c)
}

// Remove method for Department
func (d *Department) Remove(c Component) {
    for i, component := range d.components {
        if component == c {
            d.components = append(d.components[:i], d.components[i+1:]...)
            break
        }
    }
}

// ShowDetails method for Department
func (d *Department) ShowDetails() {
    fmt.Printf("%s - %s\n", d.name, d.description)
    for _, component := range d.components {
        component.ShowDetails()
    }
}

func main() {
    john := &Employee{name: "John", position: "Manager", salary: 5000.0}
    mary := &Employee{name: "Mary", position: "Developer", salary: 4000.0}
    bob := &Employee{name: "Bob", position: "Developer", salary: 4000.0}

    development := &Department{name: "Development", description: "Software development department"}
    development.Add(mary)
    development.Add(bob)

    company := &Department{name: "Company", description: "All employees"}
    company.Add(john)
    company.Add(development)

    company.ShowDetails()
}

Como puedes ver, el ejemplo es muy similar al de Java y Python, con algunas diferencias en la sintaxis debido a las particularidades del lenguaje Go. En este caso, se utilizan structs en lugar de clases, y los métodos de la interfaz Component se implementan de manera explícita en cada struct. Además, se utilizan slices en lugar de listas enlazadas para almacenar los componentes de cada Department. Sin embargo, la estructura general del patrón composite se mantiene igual.

Conclusiones

En resumen, el patrón Composite es una técnica de diseño que nos permite construir estructuras jerárquicas de objetos de manera flexible y fácilmente extensible. Es útil cuando tenemos que tratar objetos individuales y composiciones de objetos de la misma manera, y cuando necesitamos trabajar con estructuras complejas que puedan ser construidas a partir de objetos más simples. Este patrón se utiliza en la mayoría de los entornos de programación y es muy útil en la construcción de interfaces gráficas de usuario, árboles de navegación, estructuras de datos, entre otras aplicaciones.

Javier Sanchez Toledano
Escrito por Javier Sanchez Toledano Sígueme

Soy Licenciado en Informática e Ingeniero en Sistemas Computacionales.
Soy auditor líder certificado por ICA en la Norma ISO 9000, desarrollo sistemas de gestión de la calidad con un enfoque de mejora continua, creo tableros de control con indicadores clave para mejorar la toma de decisiones basadas en datos.

Comentarios