Patrón de diseño – Composite

UML[1]El patrón de diseño Composite nos sirve para construir estructuras complejas partiendo de otras estructuras mucho mas simples, dicho de otra manera, podemos crear estructuras compuestas las cuales están conformadas por otras estructuras mas pequeñas.

Para comprender mejor como funciona este patrón imaginemos una casa de ladrillos, las casas como tal no esta echa de una pieza, si observamos las paredes estas esta echas de pequeñas piezas llamadas ladrillos, entonces el conjunto de estos ladrillo crean paredes, y un conjunto de paredes crean una casa. este ejemplo puede ser aplicado al patrón Composite, y no digo que vallamos a crear una casa con este patrón, si no mas bien nos da una idea de como trabaja para poder utilizarlo con otros ejemplos.

Patrón de diseño Composite
Fig. 1: Estructura del patrón de diseño Composite.

El patrón Composite requiere mínimo de tres componentes para poder existir los cuales son Componente,Leaf o Rama y Composite.

Component: Generalmente es una interface o clase abstracta la cual tiene las operaciones mínimas que serán utilizadas, este componente deberá ser extendido por los otros dos componentes Leaf y Composite. En nuestro ejemplo esto podría representar de forma abstracta un ladrillo o toda la casa(Mas adelante comprenderemos porque)

Leaf: El leaf u hoja representa la parte mas simple o pequeña de toda la estructura y este extiende o hereda de Component. En nuestro ejemplo, este representaría un ladrillo de nuestra casa.

Composite: Aquí es donde esta la magia de este patrón, ya que el composite es una estructura conformada por otros Composite y Leaf, si vemos en la imagen 1, vemos que los Composite tiene los métodos add y remove los cuales nos permiten agregar objetos de tipo Component, Sin embargo como hablamos anteriormente, el Componente es por lo general un Interface o Clase abstracta  por lo que podremos agregamos objetos de tipo Composite o Leaf. Visto desde el punto de vista del ejemplo de la casa el Composite podría representar un conjunto de ladrillos o la casa completa, Esto desde luego seria agregando varias Ladrillo(Leaf) al Composite para crear una Pared.

No te preocupes si no lograste comprender del todo la explicación anterior, para fortalecer la explicación hablaremos de un ejemplo mas técnico donde podamos programar un escenario concreto.

Caso de estudio del patrón Composite.

Imaginemos un sistema de punto de venta, en el cual se le pueden vender al cliente una serie de productos, estos productos pueden ser productos simples (Leaf) o paquetes (Composite). El sistema permitirá crear Ordenes de ventas las cuales están compuestas por 1 o muchos productos.

La siguiente imagen ilustra la estructura de un paquete.

Patrón de diseño Composite
Fig. 2: Imagen que muestra de forma gráfica como esta compuesto un paquete, Los paquete están creados a partir de un conjunto de productos simples y otros paquetes por lo que el precio de un paquete esta calculado por el precio de sus hijos de forma recursiva.

La imagen 2 muestra la estructura de una forma conceptual, sin embargo la estructura es un poco mas compleja, ya que esta formado por una estructura de dato llamada Árbol.

Patrón de diseño Composite
Fig. 3: La imagen muestra un solo paquete es cual esta formado de otros productos simple y compuestos, un compuesto seria otro paquete, el cual tiene dentro mas productos simples. y como vimos en la figura 2, el precio de un paquete es calculado por el precio de todos los hijos de forma recursiva.

Punto importantes a tomar en cuenta:

  • Un paquete es producto compuesto de varios productos simples y otros paquetes.
  • Los paquetes y productos simples deberán ser tratados de la misma forma, por lo que deberán tener un padre en común.
  • El precio de un paquete es la suma de todos los productos simples que contenga.
  • El sistema deberá mostrar el total de la Orden y los productos que contiene.

Ya conocida la teoría, pasemos a la practica.

En primero lugar crearemos la clase la cual sera nuestro Component

 

Como podemos observar la clase AbstractProduct es una clase abstracta la cual define las características mínimas de un producto, las cuales deben tener todos los productos sin importar que sean productos simples o paquetes. Para este ejemplo, lo minino que un producto debe tener es un nombre y un precio.

En segundo lugar definimos lo que vendría siendo el Leaf o producto simple.

Lo primero que observamos es que las clases SimpleProduct extiende de AbstractProduct, luego solo como ejemplo agregamos un atributo que solo un producto simple pueda tener, en este caso, agregamos la marca, aun que en este ejemplo no nos sirve de nada nos da una idea de que podemos personalizar los productos según el tipo.

Otro punto importante es que el método getPrice no lo sobre escribimos por lo que toma el heredado de AbstractProduct el cual solo regresa el precio de la propiedad price.

Para completar el patrón tenemos el Composite, el cual es una agrupación de AbstractProduct.

Lo primero a observar en la clase CompositeProduct es que extiende tambien la clase AbstractProduct lo cual nos garantiza que tanto el SimpleProduct(Producto Simple) y el CompositeProduct(Paquete) puedan ser tratados de igual manera y nos nos interesa saber en tiempo de ejecución si es un paquete o un producto simple.

En el caso de CompositeProduct veremos que las propiedades que tiene no son las de un producto o paquete, si no que a demas de tener las que hereda de SimpleProduct, tiene una Lista de AbstractProduct, lo cual nos dice que en esta lista puede tener cualquier objeto que herede de esta clase, y para nuestro ejemplo estas clases serian SimpleProduct y CompositeProduct, Es aquí donde nos damos cuenta que un paquete puede estar compuesto por paquetes y productos simples.

Observemos también el método getPrice y setPrice, el método getPrice es sobre escrito para regresar el precio de todos los productos que contiene y el método setPrice es sobre escrito para que no nos permita establecer un precio, de esta forma cumplimos la regla de que el precio de un paquete es la suma del precio de todos los productos que contiene.

Por último veamos los métodos addProduct y removeProduct los cuales nos permite agregar y remover productos de un paquete.

Para poder realizar una venta es necesario crear un Orden de venta, por lo que se crear la clase SaleOrder la cual nos permite agregar los productos a vender.

Veamos que la clase SaleOrden no tiene mas que una lista de productos, id, fecha de venta y el nombre del cliente(Datos solo como ejemplo).

Como puntos importantes podemos ver los métodos addProduct y removeProduct, los cuales nos permiten agregar y remover productos a la Orden.

Tenemos el método getPrice, el cual nos regresa el precio total de la Orden.

PrintOrden método que imprime el detalle de la Orden en pantalla.

Ejecución.

Para finalizar este ejemplo veremos la clase Main donde veremos como funciona el ejemplo:

De la linea 5 a la 21 creamos los productos simples. Los cuales únicamente contiene el nombre del producto y el precio de venta.

Estos productos son instancias de SimpleProduct por lo que vendrían siendo el Leaf del patrón de diseño.

En las lineas 23 a la 47 podemos ver como se crean los paquetes o productos compuestos. Estos productos solo son creados con el nombre del paquete y no tiene un precio de venta. Una vez creados se les agregan los productos simples mediante el método addProduct, Si vemos bien los paquetes que creamos, veremos que todos están conformados por productos simples a excepción del paquete pc2x1 el cual es un paquete creado a partir de dos paquetes. Esto provocara que el precio de este paquete sea el precio de los dos paquetes hijos, y el precio de los hijos sera el precio de todos los productos simple que contiene, de esta forma creamos un árbol parecido al que vimos en la figura 3.

Por ultimo de la 48 a la 66 se crean 4 ordenes con productos distintos. En la orden 1 y 2 vemos que solo vendemos un paquete, en la orden 3 vendemos un paquete conformado de dos paquetes y en la orden 4 vendemos un paquete y 4 productos simples.

Resultado de la ejecución:

El resultado de la ejecución del programa nos da el siguiente resultado:

 Análisis: 

Veamos que paso.

En la orden no.1 se vendió un paquete llamado PC Gammer con un precio de  12,450 ,¿pero por qué cuesta eso?, si regresamos a la definición del paquete veremos que se le agregaron 6 productos simples: ram8gb($1,000), disk1tb($2,000), cpuIntel($4,500), bigCabinet(2,200), monitor30inch($2,000), gammerMouse($750), Si sumamos el precio de los 6 productos nos dará el total de 12,450 el mismo precio del paquete.

En la orden no.2 pasa lo mismo, se vende un paquete el cual contiene 6 productos simples pero con un precio menor.

La orden no.3 es un poco distinta, esta orden vende al igual que las anteriores un solo paquete, sin embargo este paquete pc2x1 es un paquete que se creo a partir de otros dos paquetes. En este caso el total de la orden es de 22,350, pero si vemos el precio del paquete, Gammer PC($12,450) y Home PC($9,900), veremos la suma de estos dos da el precio total del paquete pc2x1($22,350).

Por último tenemos la orden 4, esta orden explota al máximo el poder del patrón de diseño Composite, ya que nos permite vender paquetes y productos simples sin tener que preocuparnos de que tipo de productos es. ya que de forma polimorfica el programa es capas de calcular el precio de ambos tipos de productos sin necesidad de programar un trato especial.

Esta orden vende una Home PC($9,900), ram8gb($1,000),ram4gb(750),monitor30inch($2,000),gammerMouse(750), de los cuales Home PC es un paquete y el resto de los productos son productos simples. Si sumamos el precio de los productos veremos que el total es de 14,400.

Como veremos, este patrón es muy flexible con estructuras de datos compuestas, en este caso vimos que teníamos paquetes y productos simples, sin embargo podríamos crear un tercer tipo de producto que extienda de AbstracProduct y darle el comportamiento que queramos. Otro ejemplo que le podríamos dar, es para agrupar las cuentas de un cliente, de esta forma, podríamos saber el saldo de todas las cuentes de un cliente.

Si te gusto este post, por favor dale me gusta y compártelo ya que esto me ayudara a crear mas y mejor material.

 

Ya está a la venta mi nuevo libro “Introducción a los patrones de diseño“, en donde expongo los 25 patrones de diseño más utilizados y explicados desde un enfoque del mundo real, con ejemplos que pueden ser aplicados en tus proyectos. Sin duda es unos de los libros más completos de patrones de diseño en español.

Artículos relacionados

Introducción a los patrones de diseño (video) https://youtu.be/rrcCv8wwnlE En esta charla explicaré la importancia de los patrones de diseño, así como las ventajas que te traerá en tu crecimien...
Patrón de diseño Modelo Vista Controlador (MVC) Sin duda este es uno de los patrones de diseño mas utilizados cuando desarrollamos una pagina web sin embargo puede que no estemos consciente de que e...
Patrón de diseño Observer Este es sin duda uno de los patrones mas utilizados cuando trabajamos con aplicaciones de escritorio o al utilizar la Event-Driver Architecture(EDA).U...

Oscar Blancarte

Ideológico, Innovador y emprendedor, Padre, Tecnólogo y Autor, amante de la ciencia y la tecnología en todos sus colores y sabores. Arquitecto de software & Full Stack Developer con experiencia en la industria del desarrollo de software y la consultoría. Amante de la programación y el Ajedrez.

14 comentarios en “Patrón de diseño – Composite

  1. Muy buen ejemplo, estaba buscando una web que explicase este patrón de diseño con un ejemplo simple y después de un buen rato he encontrado este magnífica web.

    Sólo una duda, en el constructor de todas las clases llamas al super() siempre, aunque no herede de ninguna clase ¿lo haces por alguna razón especial?

    Muchas gracias 🙂

    1. Hola Juanma, en realidad el super() lo agrega en automáticos el mismo IDE cuando creo el constructor y en los casos donde no se hereda de ninguna clase no tiene mucho sentido pero por costumbre suelo dejarlo. Así que en estos casos, el super lo único que hace es llamar al constructor de Object, el cual es llamado en automático aunque no define la instrucción super().

  2. Profesor Oscar buenas noches,

    Gracias por tan valiosa información, apenas me inicio en la programación con Java por ello le solicito amablemente una breve explicación de la instrucción Child ya que monto el programa en eclipse para verlo funcionar y me saca error en esta línea. Mil gracias

        1. Hola Cesar, lo que vez en la línea 17 se conoce como ForEach, es una forma de recorrer los arreglos sin la necesidad de contador. Fue inicialmente introducido en el Java 1.5 por lo que si esta instrucción te esta marcando error, podría ser por que tengas una versión anterior a la 1.5. Si tienes el error que te arroja el Eclipse te podría ayudar mejor.
          saludos.

  3. Profesor Oscar buenas noches y de nuevo gracias por su ayuda,

    Este es el mensaje de error que me sale al ejecutar el programa:

    Exception in thread “main” java.lang.Error: Unresolved compilation problem:
    Type mismatch: cannot convert from element type Object to AbstractProduct

    at javamex.patronesdiseño.composite.SaleOrder.printOrder(SaleOrder.java:78)
    at javamex.patronesdiseño.composite.Main.main(Main.java:51)

    Saludos.

    1. Hola Cesar, creo que encontre el problema, intenta reemplazar la línea 8 de la clase en questión por lo siguiente:
      private List< AbstractProduct > products = new ArrayList< AbstractProduct >();

      Esto debería de quetarte el error. Si te soluciono el problema, te agradecería me lo comentaras.
      saludos.

  4. Profesor Oscar buenos días,
    Quiero confirmarle que gracias a la modificación de la línea 8 de la clase e cuestión, el programa funcionó perfectamente. Le agradezco mucho por su colaboración.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *