Mi granito de java: Patrones Estructurales
Mostrando entradas con la etiqueta Patrones Estructurales. Mostrar todas las entradas
Mostrando entradas con la etiqueta Patrones Estructurales. Mostrar todas las entradas

sábado, 4 de junio de 2011

Wrapper: Decorator vs Adapter

Es recomendable leer este post luego de haber leído los patrones Adapter y Decorator, ya que a ambos se los llama Wrapper ("envoltorio"). Es probable que quien haya leído por primera vez a ambos patrones no entiendan como es posible tamaña comparación entre ambos, siendo tan diferenciados: un decorador solo cambia las responsabilidades del objeto, no su interface. El patrón Adapter cambia el interface.

¿Entonces, porque se los llama igual? 
Bueno, les voy a mostrar un ejemplo:

Como verán es una clase común y corriente, sin nada en especial. Veamos otra clase llama AgenteEncubierto:
Prestemos atención a lo siguiente: la clase AgenteEncubierto recibo una Persona como parámetro en el constructor. Pero evidentemente el AgenteEncubierto no puede revelar ciertos datos sensibles como su apellido o dirección y por ello mismo tiene datos prefijados en sus getters.
Otros datos, como su edad no son problema, con lo cual devuelve la edad de la persona.
Funcionan de la siguiente manera:

Luego de haber visto este ejemplo, les voy a hacer una simple pregunta,¿ la clase AgenteEncubierto adapta la clase Persona o la decora?
Es en vano toda discusión, nadie se pone de acuerdo. Muchas veces se utiliza el patrón Decorator de esta forma y, como se puede observar, es una propuesta muy cercana al Patrón Adapter.
Entonces de nuevo: ¿AgenteEncubierto decora o adapta a Persona? En realidad, la "envuelve".

Decorator

El patrón decorator permite añadir responsabilidades a objetos concretos de forma dinámica. Los decoradores ofrecen una alternativa más flexible que la herencia para extender las funcionalidades.
Es conocido como Wrapper (igual que el patrón Adapter).
A veces se desea adicionar responsabilidades a un objeto pero no a toda la clase. Las responsabilidades se pueden adicionar por medio de los mecanismos de Herencia, pero este mecanismo no es flexible porque la responsabilidad es adicionada estáticamente. La solución flexible es la de rodear el objeto con otro objeto que es el que adiciona la nueva responsabilidad. Este nuevo objeto es el Decorator.

Este patrón se debe utilizar cuando:
  • Hay una necesidad de extender la funcionalidad de una clase, pero no hay razones para extenderlo a través de la herencia.
  • Se quiere agregar o quitar dinámicamente la funcionalidad de un objeto.

Dado que este patrón decora un objeto y le agrega funcionalidad, suele ser muy utilizado para adicionar opciones de "embellecimiento" en las interfaces al usuario. Este patrón debe ser utilizado cuando la herencia de clases no es viable o no es útil para agregar funcionalidad. Imaginemos que vamos a comprar una PC de escritorio. Una estándar tiene un precio determinado. Pero si le agregamos otros componentes, por ejemplo, un lector de CD, el precio varía. Si le agregamos un monitor LCD, seguramente también varía el precio. Y con cada componente adicional que le agreguemos al estándar, seguramente el precio cambiará. Este caso, es un caso típico para utilizar el Decorator.


Diagrama UML



Component: define la interface de los objetos a los que se les pueden adicionar responsabilidades dinámicamente.
ComponenteConcreteo: define el objeto al que se le puede adicionar una responsabilidad.
Decorator: mantiene una referencia al objeto Component y define una interface de acuerdo con la interface de Component.
DecoratorConcreto: adiciona la responsabilidad al Component.

Decorator propaga los mensajes a su objeto Component. Opcionalmente puede realizar operaciones antes y después de enviar el mensaje.


Ejemplo

Imaginemos que vendemos automóviles y el cliente puede opcionalmente adicionar ciertos componentes (aire acondicionado, mp3 player, etc). Por cada componente que se adiciona, el precio varía.




Bien, hasta aquí clases comunes de negoci: una interface que implementa la clase Auto y dos tipos de Auto (Ford y Fiat). Ahora veremos en que consiste el Decorator y los decoradores:





Veremos como funciona desde el punto de vista del cliente:





Consecuencias
  • Es más flexible que la herencia: utilizando diferentes combinaciones de unos pocos tipos distintos de objetos decorator, se puede crear muchas combinaciones distintas de comportamientos. Para crear esos diferentes tipos de comportamiento con la herencia se requiere que definas muchas clases distintas.
  • Evita que las clases altas de la jerarquía estén demasiado cargadas de funcionalidad.
  • Un componente y su decorador no son el mismo objeto.
  • Provoca la creación de muchos objetos pequeños encadenados, lo que puede llegar a complicar la depuración.
  • La flexibilidad de los objetos decorator los hace más propenso a errores que la herencia. Por ejemplo, es posible combinar objetos decorator de diferentes formas que no funcionen, o crear referencias circulares entre los objetos decorator.

Adapter

Busca una manera estandarizada de adaptar un objeto a otro. Se utiliza para transformar una interfaz en otra, de tal modo que una clase que no pudiera utilizar la primera, haga uso de ella a través de la segunda.
Es conocido como Wrapper (al patrón Decorator también se lo llama Wrapper, con lo cual es nombre Wrapper muchas veces se presta a confusión).
Una clase Adapter implementa un interfaz que conoce a sus clientes y proporciona acceso a una instancia de una clase que no conoce a sus clientes, es decir convierte la interfaz de una clase en una interfaz que el cliente espera. Un objeto Adapter proporciona la funcionalidad prometida por un interfaz sin tener que conocer que clase es utilizada para implementar ese interfaz. Permite trabajar juntas a dos clases con interfaces incompatibles.

Este patrón se debe utilizar cuando:
  • Se quiere utilizar una clase que llame a un método a través de una interface, pero se busca utilizarlo con una clase que no implementa ese interface.
  • Se busca determinar dinámicamente que métodos de otros objetos llama un objeto.
  • No se quiere que el objeto llamado tenga conocimientos de la otra clase de objetos.
Este patrón convierte la interfaz de una clase en otra interfaz que el cliente espera. Esto permite a las clases trabajar juntas, lo que de otra manera no podrían hacerlo debido a sus interfaces incompatibles.
Por lo general, esta situación se da porque no es posible modificar la clase original, ya sea porque no se tiene el código fuente de la clase o porque la clase es una clase de propósito general, y es inapropiado para ella implementar un interface par un propósito específico. En resumen, este patrón debe ser aplicado cuando debo transformar una estructura a otra, pero sin tocar la original, ya sea porque no puedo o no quiero cambiarla.

Diagrama UML


Target: define la interfaz específica del dominio que Cliente usa.
Cliente: colabora con la conformación de objetos para la interfaz Target.
Adaptado: define una interfaz existente que necesita adaptarse
Adapter: adapta la interfaz de Adaptee a la interfaz Target
El Cliente llama a las operaciones sobre una instancia Adapter. De hecho, el adaptador llama a las operaciones de Adaptee que llevan a cabo el pedido.

Ejemplo

Vamos a plantear el siguiente escenario: nuestro código tiene una clase Persona (la llamamos PersonaVieja) que se utiliza a lo largo de todo el código y hemos importado un API que también necesita trabajar con una clase Persona (la llamamos PersonaNueva), que si bien son bastante similares tienen ciertas diferencias:
Nosotros trabajamos con los atributos nombre, apellido y fecha de nacimiento.
Sin embargo, la PersonaNueva tiene un solo atributo nombre (que es el nombre y apellido de la persona en cuestión) y la edad actual, en vez de la fecha de nacimiento.

Para esta situación lo ideal es utilizar el Adapter. Para ello primero crearemos las 2 clases de Persona y sus correspondientes interfaces.





 Ahora crearemos al Adapter.

Y se utiliza de esta manera:

Consecuencias
  • El cliente y las clases Adaptee permanecen independientes unas de las otras.
  • Puede hacer que un programa sea menos entendible.
  • Permite que un único Adapter trabaje con muchos Adaptees, es decir, el Adapter por sí mismo y las subclases (si es que la tiene). El Adapter también puede agregar funcionalidad a todos los Adaptees de una sola vez.

Temas a tener en cuenta.
Si bien el Adapter tiene una implementación relativamente sencilla, se puede llevar a cabo con varias técnicas:
1) Creando una nueva clase que será el Adaptador, que extienda del componente existente e implemente la interfaz obligatoria. De este modo tenemos la funcionalidad que queríamos y cumplimos la condición de implementar la interfaz.
2) Pasar una referencia a los objetos cliente como parámetro a los costructores de los objetos adapter o a uno de sus métodos. Esto permite al objeto adapter ser utilizado con cualquier instancia o posiblemente muchas instancias de la clase Adaptee. En este caso particular, el Adapter tiene una implementación casi idéntica al patrón Decorator.
3) Hacer la clase Adapter una clase interna de la clase Adaptee. Esto asume que tenemos acceso al código de dicha clase y que es permitido la modificación de la misma.
4) Utilizar sólo interfaces para la comunicación entre los objetos.

Las opciones más utilizadas son la 1 y la 4.

viernes, 3 de junio de 2011

Proxy

El patrón Proxy se utiliza como intermediario para acceder a un objeto, permitiendo controlar el acceso a él. Para ello obliga que las llamadas a un objeto ocurran indirectamente a través de un objeto proxy, que actúa como un sustituto del objeto original, delegando luego las llamadas a los métodos de los objetos respectivos.

Este patrón se debe utilizar cuando:
Se necesite retrasar el coste de crear e inicializar un objeto hasta que es realmente necesario.
  • Se necesita una referencia a un objeto más flexible o sofisticada que un puntero.
  • Algunas situaciones comunes de aplicación son:
  1. Proxy remoto: representa un objeto en otro espacio de direcciones. Esto quiere decir que el proxy será utilizado de manera tal que la conexión con el objeto remoto se realice de forma controlada sin saturar el servidor.
  2. Proxy virtual: crea objetos costosos por encargo. Cuando se utiliza un software no siempre se cargan todas las opciones por default. Muchas veces se habilitan ciertos módulos sólo cuando el usuario decide utilizarlos.
  3. Proxy de protección: controla el acceso a un objeto. Controla derechos de acceso diferentes.
  4. Referencia inteligente: sustituto de un puntero que lleva a cabo operaciones adicionales cuando se accede a un objeto (ej. contar el número de referencias, cargar un objeto persistente en memoria, bloquear el objeto para impedir acceso concurrente, ...).

    El patrón Proxy es muy versátil. Puede ser utilizado en infinitas ocasiones y se le puede otorgar varios usos. Tiene una gran ventaja y es que no obliga al desarrollador a crear demasiada estructura para realizar este patrón, sino que es una forma estándar de acceder a una clase que potencialmente puede ser conflictiva. Por otro lado, no ayuda al desarrollador a crear un algoritmo, sino que el desarrollador tiene que hacer toda la lógica.

    Por estas razones, es un patrón donde no siempre se puede saber a priori cuando utilizarlo. Sin embargo, un Proxy es un concepto utilizado fuera del ámbito de los patrones de diseño: un servidor proxy es un equipo intermediario situado entre el sistema del usuario e Internet. Puede utilizarse para registrar el uso de Internet y también para bloquear el acceso a una sede Web. El servidor de seguridad del servidor proxy bloquea algunas redes o páginas Web por diversas razones. En consecuencia, es posible que no pueda descargar el entorno de ejecución de Java (JRE) o ejecutar algunos applets de Java.
    Es decir, los servidores proxy funcionan como filtros de contenidos. Y también mejoran el rendimiento: guardan en la memoria caché las páginas Web a las que acceden los sistemas de la red durante un cierto tiempo. Cuando un sistema solicita la misma página web, el servidor proxy utiliza la información guardada en la memoria caché en lugar de recuperarla del proveedor de contenidos. De esta forma, se accede con más rapidez.

    Este mismo concepto se intenta llevarlo a cabo a nivel código con el patrón Proxy. Cuando un objeto debe ser controlado de alguna manera, ya sea por simple control de acceso o por estar en un sitio remoto o por ser muy pesado y se quiera limitar su uso, es ideal utilizar este patrón.

Diagrama UML


Subject: interfaz o clase abstracta que proporciona un acceso común al objeto real y su representante (proxy).
Proxy: mantiene una referencia al objeto real. Controla la creación y acceso a las operaciones del objeto real.
RealSubject: define el objeto real representado por el Proxy.
Cliente: solicita el servicio a través del Proxy y es éste quién se comunica con el RealSubject.

Ejemplo

Vamos a realizar un ejemplo de un proxy remoto: la finalidad es que nuestra aplicació guarde datos en un servidor remoto, pero vamos a impedir se la aplicación se conecte todo el tiempo, sino que aproveche a guardar todo cuando se encuentre conectada. Caso contrario guardará en el disco duro local la información hasta que sea el momento adecuado.




Hasta ahora tenemos 2 formas de guardar: en el disco y en el objeto remoto que necesita conexión. Ambas formas implementan IGuardar. Vamos a crear un sencillo proxy para que se fije si en ese momento hay conexión para aprovecharla y si no hay conexión que guarde los datos en el disco momentaneamente.
Consecuencias
  • Introduce un nivel de dirección con diferentes usos:
  1. Un proxy remoto puede ocultar el hecho de que un objeto reside en otro espacio de direcciones.
  2. Un proxy virtual puede realizar optimizaciones, como la creación de objetos bajo demanda.
  3. Los proxies de protección y las referencias inteligentes permiten realizar tareas de mantenimiento adicionales al acceder a un objeto.
  • Copiar un objeto grande puede ser costoso. Si la copia no se modifica, no es necesario incurrir en dicho gasto.
Temas a tener en cuenta.
Este patrón no aporta demasiado en cuanto a estructura o algoritmo. Es más que nada un concepto a llevar a cabo en determinadas situaciones.

Flyweight

Busca eliminar o reducir la redundancia cuando tenemos gran cantidad de objetos que contienen información idéntica, además de lograr un equilibrio entre flexibilidad y rendimiento (uso de recursos).
Este patrón quiere evitar el hecho de crear un gran número estados de objeto para representar a un sistema. Permite compartir estados para soportar un gran número de objetos pequeños aumentando la eficiencia en espacio.

Este patrón se debe utilizar cuando:
  • Para eliminar o reducir la redundancia cuando se tiene gran cantidad de objetos que contienen la misma información.
  • Cuando la memoria es crítica para el rendimiento de la aplicación.
  • La aplicación no depende de la identidad de los objetos.

Diagrama UML



Flyweight: declara una interfaz a través de la cual los flyweights pueden recibir y actuar sobre los estados no compartidos.
ConcreteFlyweight: implementa la interfaz Flyweight y almacena los estados compartidos, si los hay. Un objeto ConcreteFlyweight debe ser compartible. Cualquier estado que almacene debe ser intrínseco; es decir, debe ser independiente de su contexto.
UnsharedConcreteFlyweight: no todas las subclases de Flyweight tienen por qué ser compartidas. La interfaz Flyweight permite que se comparta; no lo fuerza. Es común que los objetos de esta clase tengan hijos de la clase ConcreteFlyweight en algún nivel de su estructura.
FlyweightFactory: crea y gestiona los objetos flyweight. Garantiza que los objetos flyweight se comparten de forma apropiada. Cuando un cliente solicita un flyweight, el objeto de la clase FlyweightFactory proporciona una instancia existente, o crea una.
Client: contiene referencias a los flyweights. Calcula o almacena los estados no compartidos de los flyweights.


Ejemplo

Vamos a suponer el siguiente ejemplo: en un colegio tenemos 6 de promedio general.  Se busca saber que porcentaje de desviación con respecto al promedio tiene cada alumno.

A diferencia del resto de los patrones que intentan simplificar al cliente, este patrón lo obliga a conocer detalles de implementación:

 Consecuencias
  • Reduce en gran cantidad el peso de los datos en un servidor.
  • Consume un poco mas de tiempo para realizar las búsquedas.
  • Se complica la codificación lo que puede redundar en el aumento en la aparición de errores.
  • El cliente debe tener algún conocimiento de la implementación. Por esto, puede romper con la estructura cliente-servidor.

Temas a tener en cuenta.

Los clientes no deberían instanciar objetos de la clase ConcreteFlyweight directamente. Deben obtenerlos exclusivamente del objeto FlyweightFactory para garantizar que son compartidos apropiadamente.
Este patrón añade confusión a la hora de desarrollar y no está orientado con la teoría de objetos. Pero no debemos desconocer que muchas veces el tema de performance es muy importante. Por ello mismo, debemos utilizarlo con criterio y en los casos donde realmente sea necesario.

Facade

Busca simplificar el sistema, desde el punto de vista del cliente, proporcionando una interfaz unificada para un conjunto de subsistemas, definiendo una interfaz de nivel más alto. Esto hace que el sistema sea más fácil de usar.

Este patrón busca reducir al mínimo la comunicación y dependencias entre subsistemas. Para ello, utilizaremos una fachada, simplificando la complejidad al cliente. El cliente debería acceder a un subsistema a través del Facade. De esta manera, se estructura un entorno de programación más sencillo, al menos desde el punto de vista del cliente (por ello se llama "fachada").

Se debe utilizar cuando:
  • Se quiera proporcionar una interfaz sencilla para un subsistema complejo.
  • Se quiera desacoplar un subsistema de sus clientes y de otros subsistemas, haciéndolo más independiente y portable.
  • Se quiera dividir los sistemas en niveles: las fachadas serían el punto de entrada a cada nivel. Facade puede ser utilizado a nivel aplicación.
Diagrama UML



Facade:  conoce cuales clases del subsistema son responsables de una petición.
Delega las peticiones de los clientes en los objetos del subsistema.
Subsistema: manejar el trabajo asignado por el objeto Facade. No tienen ningún conocimiento del Facade (no guardan referencia de éste).

Los clientes se comunican con el subsistema a través de la facade, que reenvía las peticiones a los objetos del subsistema apropiados y puede realizar también algún trabajo de traducción. Los clientes que usan la facade no necesitan acceder directamente a los objetos del sistema.

Ejemplo

Imaginemos que estamos, con un equipo de desarrollo, realizando el software para una inmobiliaria. Obviamente una inmobiliaria realiza muchos trabajos diferentes, como el cobro de alquiler, muestra de inmuebles, administración de consorcios, contratos de ventas, contratos de alquiler, etc.
Por una cuestión de seguir el paradigma de programación orientada a objetos, es probable que no se realice todo a una misma clase, sino que se dividen las responsabilidades en diferentes clases.

Imaginemos que en el soft de la inmobiliaria tenemos diversos tipos de Personas, todas con sus atributos y métodos correspondientes. Aca pondré sólo un resumen de ellas, ya que no tiene sentido ahondar en demasiados detalles de implemetación.

Lo mismo con los métodos principales de las diversas clases que tiene el sistema:

Ahora haremos una clase llamada Inmobiliaria que será nuestro Facade:

Bien, veamos ahora 2 tipos de clientes. El primero no llamará al Facade y el segundo si lo utilizará. Veremos como el primero esta obligado a conocer muchos detalles de los subsistemas y el segundo no.



Consecuencias.
  • Oculta a los clientes de la complejidad del subsistema y lo hace fácil de usar.
  • Favorece un acoplamiento débil entre el subsistema y sus clientes, consiguiendo que los cambios de las clases del sistema sean transparentes a los clientes.
  • Facilita la división en capas y reduce dependencias de compilación.
  • No se impide el acceso a las clases del sistema.

Temas a tener en cuenta.
Lo más importante de todo es que este patrón se debe aplicar en las clases más representativas y no en las específicas. De no ser así, posiblemente no se tenga el nivel alto deseado.
Por aplicación, es ideal construir no demasiados objetos Facade. Sólo algunos representativos que contengan la mayoría de las operaciones básicas de un sistema.

jueves, 2 de junio de 2011

Bridge

Desacopla una abstracción de su implementación, de manera que ambas puedan variar de forma independiente. ¿Que quiere decir exactamente esto? Una abstracción se refiere a un comportamiento que una clase debería implementar, ya sea porque esta obligada por una interface o una clase abstracta. Por otro lado, con implementación se refiere a colocarle lógica a dicha obligación.
Con lo cual, este patrón permite modificar las implementaciones de una abstracción en tiempo de ejecución. Básicamente es una técnica usada en programación para desacoplar la interface de una clase de su implementación, de manera que ambas puedan ser modificadas independientemente sin necesidad de alterar por ello la otra.

Cuando un objeto tiene unas implementaciones posibles, la manera habitual de implementación es el uso de herencias. Muchas veces la herencia se puede tornar inmanejable y, por otro lado, acopla el código cliente con una implementación concreta. Este patrón busca eliminar la inconveniencia de esta solución.

Este patrón debe ser utilizado cuando:
  • Se desea evitar un enlace permanente entre la abstracción y su implementación. Esto puede ser debido a que la implementación debe ser seleccionada o cambiada en tiempo de ejecución.
  • Tanto las abstracciones como sus implementaciones deben ser extensibles por medio de subclases. En este caso, el patrón Bridge permite combinar abstracciones e implementaciones diferentes y extenderlas independientemente.
  • Cambios en la implementación de una abstracción no deben impactar en los clientes, es decir, su código no debe tener que ser recompilado.
  • Se desea compartir una implementación entre múltiples y este hecho debe ser escondido a los clientes.
  • Permite simplificar jerarquías demasiado pobladas.

Diagrama UML


Abstraction: define una interface abstracta. Mantiene una referencia a un objeto de tipo Implementor.
RefinedAbstraction: extiende la interface definida por Abstraction
Implementor: define la interface para la implementación de clases. Esta interface no se tiene que corresponder exactamente con la interface de Abstraction; de hecho, las dos interfaces pueden ser bastante diferentes entre sí. Típicamente la interface Implementor provee sólo operaciones primitivas, y Abstraction define operaciones de alto nivel basadas en estas primitivas.
ImplementadorConcreto: implementa la interface de Implementor y define su implementación concreta.

Abstraction emite los pedidos de los clientes a su objeto Implementor. El cliente no tiene que conocer los detalles de la implementación.

Ejemplo

Imaginemos que necesitamos dibujar distintas figuras geométricas (círculo, rectángulo, etc). Cada figura geométrica puede ser dibujada con diferentes tipos de líneas (normal, punteada, etc).

¿Que alternativas tenemos?

Por ejemplo, podría hacer una clase dibujo con todos los tipos de dibujos posibles. Pero a medida que me agreguen dibujos y formas, se va a parecer a un antipatrón (the God Class). Con esto quiere decir, que sería inmanejable.
Entonces podría hacer la clase Círculo y Rectángulo y que ellas dibujen su propia forma. Pero que pasa si me modifican el Dibujo en sí; por ejemplo, el espesor de la línea punteada tiene que se más grueso...¿porque un círculo debería tener conocimientos tan finos de dibujo?
Se podrían pensar más alternativas pero ningua terminará de satisfacer en un 100% nuestro problema. Es por ello que existe el Bridge.

Vamos a combinar las clases de tal forma que tanto un Círculo como un Rectángulo sepan como dibujarse de manera abstracta, pero le dejaremos la implementación a un especialista en dibujo.



Obviamente estas clases no debería realizar una simple salida por consola sino que debería poseer el algoritmo del dibujo, pero nos sirve a modo de ejemplo.


Bien, ya tenemos las implementaciones de ambos dibujos. Ahora veamos de utilizarlas en las clases correspondientes.


Veamos como se llama desde el cliente:



Consecuencias
  • Desacopla interface e implementación: una implementación no es limitada permanentemente a una interface. Le es posible a un objeto cambiar su implementación en tiempo de ejecución. Este desacoplamiento fomenta las capas, que pueden conducir a un sistema mejor estructurado.
  • La parte de alto nivel de un sistema sólo tiene que conocer Abstraction e Implementor.
  • Mejora la extensibilidad: se puede extender las jerarquías de Abstraction e Implementor independientemente.
  • Esconde los detalles de la implementación a los clientes

miércoles, 1 de junio de 2011

Composite

El patrón Composite sirve para construir algoritmos u objetos complejos a partir de otros más simples y similares entre sí, gracias a la composición recursiva y a una estructura en forma de árbol. Dicho de otra forma, permite construir objetos complejos componiendo de forma recursiva objetos similares en una estructura de árbol. Esto simplifica el tratamiento de los objetos creados, ya que al poseer todos ellos una interfaz común, se tratan todos de la misma manera.

Este patrón busca representar una jerarquía de objetos conocida como “parte-todo”, donde se sigue la teoría de que las "partes" forman el "todo", siempre teniendo en cuenta que cada "parte" puede tener otras "parte" dentro.

Se debe utilizar este patrón cuando:
  • Se busca representar una jerarquía de objetos como “parte-todo”.
  • Se busca que el cliente puede ignorar la diferencia entre objetos primitivos y compuestos (para que pueda tratarlos de la misma manera).

Diagrama UML


Component: implementa un comportamiento común entre las clases y declara una interface de manipulación a los padres en la estructura recursiva.
Leaf: representa los objetos “hoja” (no poseen hijos). Define comportamientos para objetos primitivos.
Composite: define un comportamiento para objetos con hijos. Almacena componentes hijos. implementa operaciones de relación con los hijos.
Cliente: manipula objetos de la composición a través de Component.
Los clientes usan la interfaz de Component para interactuar con objetos en la estructura Composite. Si el receptor es una hoja, la interacción es directa. Si es un Composite, se debe llegar a los objetos “hijos”, y puede llevar a utilizar operaciones adicionales.

Ejemplo

Vamos a realizar un ejemplo de un Banco. Un banco puede tener muchos sectores: Gerencia, Administrativo, RRHH, Cajas, etc. Cada uno de estos sectores tendrá empleados que cobran un sueldo. En nuestro caso utilizaremos el Composite para calcular la sumatoria de sueldos de la empresa. Para ello definimos la Interface y el Composite.

En la clase Composite está todo el secreto del patrón: contiene una colección de hijos del tipo ISueldo que los va agregando con el método agrega(ISueldo) y cuando al composite le ejecutan el getSueldo() simplemente recorre sus hijos y les ejecutas el método. Sus hijos podrán ser de 2 tipos: Composite (que a su vez harán el mismo recorrido con sus propios hijos) u Hojas que devolverán un valor.
En nuestro ejemplo hay varios sectores, solo pongo uno para no llenar de pics el ejemplo. Pero todos son muy parecidos: la única relación que tienen con el composite es que heredan de él.
Ahora veamos el caso del empleado que trabaja en el banco (sería una hoja):

Listo, ahora veamos la forma de concatenar todo. Yo lo hice en el Main (que vendría a representar al cliente), pero en realidad el armado del banco no siempre lo debe hacer el cliente. De hecho, podríamos utilizar un patrón creacional para que nos ayude en el armado.

El banco se compone de varios sectores, los cuales pueden tener personas o más sectores adentro.


Consecuencias.
  • Define jerarquías entre las clases.
  • Simplifica la interacción de los clientes.
  • Hace más fácil la inserción de nuevos hijos.
  • Hace el diseño más general.
  • Si la operación es compleja, puede ensuciar mucho el código y hacerlo ilegible.
Temas a tener en cuenta

Hay muchas formas de llevar a cabo este patrón. Muchas implementaciones implican referencias explicitas al padre para simplificar la gestión de la estructura.
Si el orden de los hijos provoca problemas utilizar Iterator.Para borrar elementos les recomiendo que un objeto elimine a sus hijos.
La mejor estructura para almacenar elementos depende de la eficiencia: si se atraviesa la estructura con frecuencia se puede dejar información en los hijos.