martes, 22 de enero de 2008

Decorador (GoF) ó agregando responsabilidades a un objeto de forma dinámica

Las clases que componen la estructura del patrón decorador:



Comencemos por recordar un principio básico de diseño (abierto-cerrado): “las clases deben ser abiertas para la extensión pero cerradas para la modificación”.
Nuestro patrón GoF se basa en este principio (como tantos otros); para entender su implementación veamos una problemática para ejemplificar.

Tenemos una heladería, la cual además de vender variados gustos en distintos envases (copón, cucuruchos, vasitos), permite agregar diversos dulces al helado, por ej.: bañado de cobertura de chocolate, pasas de uva, almendras, nueces, granolas, rocklets, etc.
Por lo tanto el precio del producto variará en función de los adicionales agregados, por ej.: un cucurucho con nueces y rocklets se vende a 6$ (4$ del producto en sí + 2$ de agregados), un vasito con cobertura de chocolate se vende a 4$ (3$ del producto y 1$ la cobertura).

Nuestro diseño rígido:

Cada método Costo() necesita calcular el costo del tipo de producto y luego agregar los adic. llamando a la implementación de Costo() de la superclase.


Problemas de este diseño?
  • Al tener que agregar ó sacar adicionales, nos forzará a cambiar el método Costo() en la superclase y también agregar ó sacar las propiedades respectivas.

  • Podríamos tener que en sólo alguno de los tipos de productos como el “Copón” , se pudiera agregar un determinado adicional, como por ej.: “ensalada de frutas”.

  • Que pasaría si alguien quiere doble porción de “rocklets”?

Como se ve, la problemática para obtener el precio del producto, resulta interesante para diseñar, debido a las posibilidades de cambio en el algortimo que debe determinar su valor. Ante esta “posibilidad de cambio en el código” actual ó a futuro, debemos comenzar a pensar en alguna solución de diseño que nos permita la flexibilidad necesaria para incorporar los cambios sin tener que recodificar lo ya hecho, o sea con el menor impacto en nuestro código.

Apliquemos el patron para flexibilizar nuestro diseño:



Este diseño implica que cualquiera de nuestros productos concretos (copón, cucurucho,etc), puede tener 1 ó más objetos decoradores (Granola, Rocklet, etc.). De esta forma podría comprar un copón con doble ración de rocklets y nueces, ó cualquier combinación que desee, para luego solicitar su costo; el cual iría sumando los precios de los agregados hasta llegar al precio del producto concreto.

Veamos un ejemplo en código C#:

** nuestro producto Copón tiene una descrip. apropiada y un costo de 4.50$
public class Copon: Producto
{
public Copon()
{ descripcion=”Copón helado”;
precio=4.50;}

public decimal Costo()
{ return this.precio; }
}

** nuestro agregado de Rocklets hereda de Decorador
** debemos pasarle el producto a decorar en su constructor
** El costo de agregar rocklets a los helados es de 0.80$, y
** lo sumamos al costo del producto a decorar...

public class Rocklet: Decorador
{
Producto producto;

public Rocklet(Producto prod)
{ this.producto=prod;
this.precio=0.80;}

public decimal Costo()
{ return this.precio + this.producto.Costo();}
}
(** Supongamos que el adicional de nueces tiene un costo de 1$.)

Veamos como usarlo en un código cliente:

.....
Producto prod1=new Copon();
prod1=new Rocklet(prod1);
prod1=new Nuez(prod1);


** podemos agregar una nueva ración de rocklets?, claro

prod1=new Rocklet(prod1);

** mostramos el costo total del Copon con doble rocklets y nuez
System.out.println(prod1.Costo());
.....
Debería mostrar: 6.30$ (4.50+0.80+1), cabe aclarar que el método Costo() de prod1 se ejecuta recursivamente para ir capturando el precio del producto a decorar.