Como ya dijimos en el post anterior los Patrones son formas de escribir código para resolver algún problema que se presenta con regularidad. En este caso el Patron Decorador resuelve el siguiente problema:
A veces tenemos la necesidad de crear una clase base, que hace ciertas cosas, y queremos extender la funcionalidad de esa clase base, pues fácil, usamos la herencia. Pero después queremos agregarle otra funcionalidad distinta. La solución seria heredar de nuevo. Y podemos seguir así una y otra vez. Al final tendremos una clase monstruo que hace muchas cosas de las cuales la mayoría de las veces solo ocuparemos una pocas.
Una forma de resolverlo es con herencia múltiple, es decir creando una clase que herede de varias clases, solo las que tengan la funcionalidad que nos interesa. Pero la herencia múltiple tiene sus desventajas, el principal es el problema del diamante en el cual no vamos a ahondar. Es por ello que muchos lenguajes modernos han descartado la herencia multiple, por ejemplo C# y Java.
El Patrón Decorador es una forma de resolver este problema. Como su nombre lo indica este Patrón esta hecho para decorar una clase base, es como una envoltura que podemos colocar sobre una clase y que le agrega funcionalidad a esta, estas envolturas las podemos poner en el orden que queramos, combinarlas, ponerlas mas de una vez e inclusive quitarlas.
Comencemos con el ejempo: Vamos a suponer que estamos haciendo un sistema de tratamiento de imagenes. Nos interesa crear una clase que convierta una imagen en escala de grises, otra que invierta los colores de la imagen y otra que la rote 90 grados.
Lo primero es tener una clase base abstracta, es decir que no implemente ninguna funcionalidad:
class ImagenBase { public: virtual void Render() = 0; }; |
Luego creamos nuestra clase principal, en ella ponemos todo lo que creamos que básico y que siempre se va a usar. En nuestro ejemplo vamos a crear la función Render(), que para no complicar el código va a escribir un texto.
class Imagen : public ImagenBase { public: void Render() { cout << "Imagen de un gato"; } }; |
Luego creamos otra clase abstracta, que hereda de la primer clase abstracta. Pero esta debe aceptar en su constructor un apuntador de la clase ImagenBase, esta clase que vamos a pasar, es la clase que vamos a decorar.
class ImagenDecorador : public ImagenBase { protected: ImagenBase * Base; public: ImagenDecorador(ImagenBase* Base): Base(Base) {}; virtual void Render() = 0; }; |
Notese que hay que guardar una referencia de la clase que vamos a decorar, esto lo hacemos en la variable Base.
Y hemos terminado lo principal. Ahora resta implementa nuestros decoradores, en ellos implementamos las características adicionales que va a tener la clase Imagen.
Por ejemplo implementamos una clase que convierte a escala de grises (ojo en realidad solo vamos a poner texto, esto es solo un ejemplo):
class ImagenGris : public ImagenDecorador { public: ImagenGris(ImagenBase* Base) : ImagenDecorador(Base) {}; void Render() { cout << "gris("; Base->Render(); cout << ")"; } }; |
Y una clase mas que invierte los colores de la imagen:
class ImagenInvertida : public ImagenDecorador { public: ImagenInvertida(ImagenBase* Base) : ImagenDecorador(Base) {}; void Render() { cout << "invertida("; Base->Render(); cout << ")"; } }; |
Y por ultimo una clase que rota la imagen 90 grados:
class ImagenRotada : public ImagenDecorador { public: ImagenRotada(ImagenBase* Base) : ImagenDecorador(Base) {}; void Render() { cout << "rotada90("; Base->Render(); cout << ")"; } }; |
Listo, ahora vamos a ver como se usan. Supongamos que queremos que
1.- convertir una imagen a grises y
2.- queremos rotarla noventa grados,
3.- invertir los colores y
4.- de nuevo rotarla 90 grados.
Hacemos lo siguiente:
ImagenBase * imagenTratada = new ImagenRotada(new ImagenInvertida(new ImagenRotada(new ImagenGris(new Imagen)))); imagenTratada->Render(); |
Y esa es la idea básica, aunque se puede mejorar mucho, por ejemplo nota que no es fácil hacer limpieza de memoria, porque no hicimos un destructor, pero lo podemos poner en la clase ImagenDecorador, también no hace falta un método para obtener de nuevo la clase que estamos decorando de modo que podemos eliminar los decoradores. Pero bueno lo básico del Patron Decorador ya esta ilustrado.