Patrón de diseño Composite en Java

El patrón de diseño Composite permite crear objetos a partir de una interfaz haciendo que todos sean similares y permitiendo que todos sean tratados de la misma manera.

Cuales son las partes del patrón de diseño Composite

Veamos las partes de este patrón Composite:

  • Componente (Composite): es la interfaz o clase abstracta de la cual se implementan las clases.
  • Hoja (Leaf ): es el objeto que fue implementado a partir del componente.
  • Composición: componente con hijos / hojas.
  • Cliente: es la clase que maneja el composite a través de la interfaz de Componente.

Piensa en este componente como en una estructura de árbol de este modo.

Observa que una composición puede componerse de otras composiciones o simplemente de un solo hijo u hoja. Todas, tanto las composiciones como las hojas pueden ser tratadas de la misma manera a través de su interfaz.

Composite Patter Tree Java

Como se crea el patrón de diseño Composite en Java

Un ejemplo simple de uso común para comprender mejor este patrón es el ejemplo típico de Empleados y Subordinados con sus diferentes roles dentro.

Aquí tenemos un ejemplo de un Composite Employee del cual se crear un Manager y Developer. Un Manager maneja varios developers que dependen de él.

Composite Example Java

Definamos nuestra interfaz de la cual implementaremos luego otros componentes.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package patterns.composite;

import java.util.List;

public interface Employee {

    String getName();

    void add(Employee e);

    void remove(Employee e);

    List<Employee> getEmployees();

    int calculatePoints();

}

Ahora definimos el componente Manager que implementa la interfaz.

Un manager es un composite que tiene una lista de developers a cargo.

Ambas clases, Manager y Developer implementan la interfaz Employee.

Composite Example

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package patterns.composite;

import java.util.ArrayList;
import java.util.List;

public class Manager implements Employee {

    private List<Employee> employees = new ArrayList<>();
    private String name;

    public Manager(String name) {
        this.name = name;
    }

    @Override
    public List<Employee> getEmployees() {
        return this.employees;
    }

    @Override
    public void add(Employee e) {
        if (e != null) {
            this.employees.add(e);
        }
    }

    @Override
    public void remove(Employee e) {
        if (e != null) {
            this.employees.remove(e);
        }
    }

    @Override
    public int calculatePoints() {
        if (this.employees.isEmpty()) {
            return 0;
        }
        return Math.round(this.employees.stream().mapToInt(e -> {
            System.out.println(e);
            return e.calculatePoints();
        }).sum());
    }

    @Override
    public String getName() {
        return this.name;
    }

}

Definamos la clase Developer que también implementa la interfaz Employee.

Observa que un manager puede estar compuesto por otros employee, en este caso developers.

Un developer, suponemos en este ejemplo, que no puede tener otros employee a cargo; por lo que los métodos para agregar elementos a la lista no tienen funcionalidad.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package patterns.composite;

import java.util.List;
import java.util.Random;

public class Developer implements Employee {

    String name;

    public Developer(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return this.name;
    }

    @Override
    public void add(Employee e) {
    // nothing to implement
    }

    @Override
    public void remove(Employee e) {
    // nothing to implement    
    }

    @Override
    public List<Employee> getEmployees() {
    // nothing to implement
     return null;
    }

    @Override
    public int calculatePoints() {
        return new Random().nextInt(24);
    }

    @Override
    public String toString() {
        return "I am " + getName() + ", Developer";
    }

}

Probando el el composite con el cliente.

Definimos la clase ClientComposite que hará uso del componente Employee.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package patterns.composite;

public class ClientComposite {

    public static void main(String... args) {

        Employee m1 = new Manager("Marc");

        Employee d1 = new Developer("Maria");
        Employee d2 = new Developer("Ema");
        Employee d3 = new Developer("Brian");

        m1.add(d1);
        m1.add(d2);
        m1.add(d3);

        Employee m2 = new Manager("Susan");

        Employee d4 = new Developer("James");
        Employee d5 = new Developer("Michael");

        m2.add(d4);
        m2.add(d5);

    }
}
I am Maria, Developer
I am Ema, Developer
I am Brian, Developer
19
I am James, Developer
I am Michael, Developer
20

Conclusión:

Vimos que el patrón composite nos sirve para representar jerarquías y para unificar las operaciones entre los objetos y los objetos individuales a fin de tratarlos a todos de la misma manera simplificando su uso.

La desventaja de este patrón composite es que que muchas veces debes definir métodos generales cuya implementación estarán vacíos en algunos casos, tal el caso de la clase developer que vimos.

Puedes descargar este código aqui

github

gitlab