Design Pattern Composite - FernandoCalmet/csharp-essential GitHub Wiki
El patrón de diseño compuesto consta de las siguientes partes:
- Componente
- Hoja
- Compuesto
Un componente es una interfaz que describe operaciones que son comunes a elementos simples o complejos del árbol.
Una hoja es un objeto único, que no tiene subelementos. Nuestra estructura de árbol consta de más objetos hoja.
Compuesto es un objeto que tiene subelementos (hojas u otros objetos compuestos). Lo interesante es que el objeto compuesto no está familiarizado con las clases concretas de sus hijos. Se comunica con sus hijos a través de la interfaz del componente.
Finalmente, tenemos un cliente, que trabaja con todos los elementos a través de la interfaz Component.
Pero basta de teoría, comencemos con el ejemplo concreto.
Imaginemos que necesitamos calcular el precio total de un regalo que estamos vendiendo en nuestra tienda. El regalo puede ser un solo elemento (juguete) o puede ser un regalo complejo que consiste en una caja con dos juguetes y otra caja con quizás un juguete y la caja con un solo juguete adentro. Como podemos ver, tenemos una estructura de árbol que representa nuestro regalo complejo, por lo que implementar el patrón de diseño compuesto será la solución adecuada para nosotros.
Entonces, comencemos con la parte Component:
public abstract class GiftBase
{
protected string name;
protected int price;
public GiftBase(string name, int price)
{
this.name = name;
this.price = price;
}
public abstract int CalculateTotalPrice();
}Podemos ver que nuestro componente consta de dos campos protegidos y un método abstracto. Estos campos y método se utilizarón como interfaz entre la hoja y la parte compuesta de nuestro patrón.
Ahora, en muchos ejemplos, podemos ver operaciones adicionales como agregar y eliminar dentro de la clase abstracta, pero no las vamos a agregar en esta clase, porque nuestra clase Leaf no las necesita. Lo que vamos a crear en cambio es una nueva interfaz - IGiftOperations:
public interface IGiftOperations
{
void Add(GiftBase gift);
void Remove(GiftBase gift);
}Solo nuestro objeto compuesto implementará esta interfaz, pero el objeto hoja no lo hará. Esto es mucho mejor porque nuestro objeto hoja no necesita implementar los métodos que no usará.
Entonces, continuemos con la clase Composite:
public class CompositeGift : GiftBase, IGiftOperations
{
private List<GiftBase> _gifts;
public CompositeGift(string name, int price)
:base(name, price)
{
_gifts = new List<GiftBase>();
}
public void Add(GiftBase gift)
{
_gifts.Add(gift);
}
public void Remove(GiftBase gift)
{
_gifts.Remove(gift);
}
public override int CalculateTotalPrice()
{
int total = 0;
Console.WriteLine($"{name} contains the following products with prices:");
foreach (var gift in _gifts)
{
total += gift.CalculateTotalPrice();
}
return total;
}
}La implementación de la clase es bastante sencilla. Primero, tenemos la lista GiftBase de tipos en la que almacenamos nuestra hoja u otros objetos compuestos. Podemos agregar o eliminar esos objetos de nuestra lista implementando métodos Add y Remove desde nuestra interfaz IGiftOperations. Finalmente, estamos calculando el precio total de nuestro objeto Gift con todos los sub-regalos dentro de él.
Continuemos con la parte de la hoja:
public class SingleGift : GiftBase
{
public SingleGift(string name, int price)
:base(name, price)
{
}
public override int CalculateTotalPrice()
{
Console.WriteLine($"{name} with the price {price}");
return price;
}
}Y eso es todo lo que necesitamos para la implementación Leaf porque no tiene subniveles, por lo que no requiere agregar o eliminar funciones en absoluto.
Finalmente, podemos implementar nuestra parte de cliente:
class Program
{
static void Main(string[] args)
{
var phone = new SingleGift("Phone", 256);
phone.CalculateTotalPrice();
Console.WriteLine();
//composite gift
var rootBox = new CompositeGift("RootBox", 0);
var truckToy = new SingleGift("TruckToy", 289);
var plainToy = new SingleGift("PlainToy", 587);
rootBox.Add(truckToy);
rootBox.Add(plainToy);
var childBox = new CompositeGift("ChildBox", 0);
var soldierToy = new SingleGift("SoldierToy", 200);
childBox.Add(soldierToy);
rootBox.Add(childBox);
Console.WriteLine($"Total price of this composite present is: {rootBox.CalculateTotalPrice()}");
}
}Podemos ver que estamos creando un regalo con un solo elemento dentro y un regalo complejo con los juguetes y una caja adicional con un solo juguete dentro.
Entonces, tan pronto como ejecutemos nuestra aplicación, obtendremos este resultado:

Excelente. Eso lo envuelve para el patrón de diseño compuesto.