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.
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 crean un Manager y Developer. Un Manager maneja varios developers que dependen de él.
Definamos nuestra interfaz de la cual implementaremos luego otros componentes.
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.
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.
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.
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.