Si quieres ampliar las fu­n­cio­na­li­da­des de una clase en un software orientado a objetos, tienes dos opciones. La opción más fácil es im­ple­me­n­tar subclases que co­m­ple­me­n­ten a la clase base. Sin embargo, esta opción puede volverse confusa. Como al­te­r­na­ti­va, puedes usar una entidad de­co­ra­do­ra según el llamado patrón de diseño Decorador (Decorator Design Pattern). El patrón, que es uno de los 23 Gang of Four (GoF) Design Patterns, permite una expansión dinámica de las clases mientras el software está en fu­n­cio­na­mie­n­to. Es más, el patrón Decorador funciona sin recurrir a je­ra­r­quías de herencia largas y de difícil co­m­pre­n­sión.

A co­n­ti­nua­ción, te ex­pli­ca­re­mos qué es exac­ta­me­n­te un patrón de diseño Decorador y cuáles son sus ventajas y de­s­ve­n­ta­jas y mo­s­tra­re­mos su mecánica mediante un gráfico y un ejemplo práctico.

¿Qué es un patrón Decorator?

El patrón de diseño Decorador es una es­tra­te­gia de diseño revelada en 1994 para expandir la fu­n­cio­na­li­dad de las clases di­ná­mi­ca­me­n­te en el software de co­mpu­tación orientado a objetos. Según este patrón, cualquier objeto puede co­m­ple­me­n­tar­se con un co­m­po­r­ta­mie­n­to deseado sin influir en las fu­n­cio­na­li­da­des de otros objetos de la misma clase. Desde el punto de vista es­tru­c­tu­ral, el patrón Decorador tiene mucho en común con el patrón Cadena de Re­s­po­n­sa­bi­li­dad (Chain of Re­s­po­n­si­bi­li­ty), aunque, en contraste con este, con un pro­ce­sa­dor central, todas las clases reciben las pe­ti­cio­nes.

El co­m­po­ne­n­te de software que hay que ampliar se “decora” con una o más clases de­co­ra­do­ras, que lo envuelven co­m­ple­ta­me­n­te, siguiendo el patrón de diseño Decorador. Cada decorador es del mismo tipo que el co­m­po­ne­n­te al que envuelve y, por lo tanto, tiene la misma interfaz. De esta manera, las llamadas de método entrantes pueden delegarse fá­ci­l­me­n­te al co­m­po­ne­n­te adjunto mientras lleva a cabo una fu­n­cio­na­li­dad. Las llamadas también pueden pro­ce­sar­se dentro del decorador.

¿Cuál es la finalidad del patrón de diseño Decorator?

Al igual que otros patrones GoF, como, por ejemplo, el patrón Strategy o el Builder, el patrón Decorator tiene como objetivo hacer que los co­m­po­ne­n­tes del software orientado a objetos sean más flexibles y fáciles de re­uti­li­zar. Con este fin, el enfoque permite a los de­sa­rro­lla­do­res añadir y eliminar las de­pe­n­de­n­cias de un objeto de manera dinámica y, cuando sea necesario, durante el tiempo de ejecución. Esto convierte al nombrado patrón en una buena al­te­r­na­ti­va al uso de subclases, las cuales pueden co­m­ple­me­n­tar a una clase, pero no permiten hacer ajustes durante el tiempo de ejecución.

Nota

Un co­m­po­ne­n­te de software puede ampliarse con un número cua­l­quie­ra de clases de­co­ra­do­ras. Estas ex­te­n­sio­nes son in­vi­si­bles, lo que significa que no­r­ma­l­me­n­te es difícil percibir si una clase real está precedida por clases adi­cio­na­les.

El patrón Decorador como diagrama UML

El decorador, o las clases decorador (Co­n­cre­te­De­co­ra­tor), tiene la misma interfaz que el co­m­po­ne­n­te de software a decorar (Co­n­cre­te­Co­m­po­ne­nt) y es del mismo tipo. Estos son re­qui­si­tos im­po­r­ta­n­tes a la hora de manejar las llamadas, que pueden remitirse con o sin cambios en función de si el decorador no se encarga de pro­ce­sar­las. En este concepto, esta interfaz elemental, que es ese­n­cia­l­me­n­te una su­pe­r­cla­se abstracta, recibe el nombre de “co­m­po­ne­n­te”.

La in­ter­ac­ción entre el co­m­po­ne­n­te básico y el decorador es fá­ci­l­me­n­te visible si se ilustra con un diagrama de clases UML. Hemos utilizado este lenguaje de modelado para la pro­gra­ma­ción orientada a objetos en el diagrama UML a co­n­ti­nua­ción para entender el patrón Decorator:

Pros y contras del patrón de diseño Decorador

Co­n­si­de­rar el patrón de diseño Decorator al diseñar un programa es útil por varias razones. En primer lugar, utilizar la es­tru­c­tu­ra Decorador conlleva un alto grado de fle­xi­bi­li­dad: las fu­n­cio­na­li­da­des de las clases pueden ampliarse durante la co­m­pi­la­ción y durante el tiempo de ejecución sin necesidad de recurrir a una jerarquía de clases basadas en la herencia. Esto mejora si­g­ni­fi­ca­ti­va­me­n­te la le­gi­bi­li­dad del código del programa.

Debido a que la fu­n­cio­na­li­dad se divide en varias clases de­co­ra­do­ras, el re­n­di­mie­n­to del software puede in­cre­me­n­tar­se. Esto facilita la re­cu­pe­ra­ción e ini­cia­ción de funciones es­pe­cí­fi­cas. Con una clase base compleja que pro­po­r­cio­na pe­r­ma­ne­n­te­me­n­te todas las funciones, esta opción de recursos op­ti­mi­za­dos no está di­s­po­ni­ble.

Sin embargo, de­sa­rro­llar usando el patrón Decorador tiene también algunas de­s­ve­n­ta­jas. Con la in­tro­du­c­ción del patrón aumenta la co­m­ple­ji­dad del software de forma au­to­má­ti­ca. La interfaz Decorador, en pa­r­ti­cu­lar, suele contener mucho texto y términos nuevos, lo que aumenta su co­m­ple­ji­dad. Otra de­s­ve­n­ta­ja es el gran número de objetos Decorador, razón por la cual se re­co­mie­n­da una si­s­te­ma­ti­za­ción separada para evitar problemas de vi­sua­li­za­ción al trabajar con subclases. Por último, las largas cadenas de llamadas de los objetos decorados (es decir, los co­m­po­ne­n­tes ampliados) suelen di­fi­cu­l­tar la detección de errores y por ende también el proceso de de­pu­ra­ción.

Ventajas De­s­ve­n­ta­jas
Alto grado de fle­xi­bi­li­dad Alta co­m­ple­ji­dad del software (es­pe­cia­l­me­n­te la interfaz Decorador)
Am­plia­ción de las funciones de las clases sin herencia De difícil co­m­pre­n­sión al principio
Código de programa legible Alto número de objetos
Fu­n­cio­na­li­da­des op­ti­mi­za­das para los recursos Proceso de de­pu­ra­ción difícil

Patrón Decorator: ejemplos

El patrón Decorador pro­po­r­cio­na la base para im­ple­me­n­tar objetos dinámicos, tra­n­s­pa­re­n­tes y ex­pa­n­di­bles en un programa. En pa­r­ti­cu­lar, los co­m­po­ne­n­tes de las in­te­r­fa­ces gráficas de usuario (GUI) figuran como áreas de apli­ca­ción típicas del patrón. Si, por ejemplo, se debe enmarcar un campo de texto, basta con emplazar un decorador “invisible” entre el objeto campo de texto y la llamada para insertar el nuevo elemento de la interfaz.

Un ejemplo muy conocido de la im­ple­me­n­ta­ción del patrón Decorador lo co­n­s­ti­tu­yen las llamadas clases stream de la bi­blio­te­ca de Java, re­s­po­n­da­bles de gestionar la entrada y salida de datos. Aquí, las clases Decorador se utilizan para añadir nuevas pro­pie­da­des e in­fo­r­ma­ción de estado al flujo de datos o para pro­po­r­cio­nar nuevas in­te­r­fa­ces.

Java no es el único lenguaje de pro­gra­ma­ción que hace un uso ge­ne­ra­li­za­do del patrón Decorador. Los si­guie­n­tes lenguajes de pro­gra­ma­ción también lo utilizan:

  • C++
  • C#
  • Go
  • Ja­va­S­cri­pt
  • Python
  • PHP

Apli­ca­ción práctica del patrón Decorator

Como acabamos de ver, el patrón Decorador no es adecuado para todos los tipos de software. Sin embargo, cuando una clase tiene que mo­di­fi­car­se a po­s­te­rio­ri, y es­pe­cia­l­me­n­te en proyectos en los que esto no se puede conseguir mediante el uso de subclases, el patrón Decorador es una gran solución.

En el ejemplo que pre­se­n­ta­mos a co­n­ti­nua­ción, el punto de partida es un programa que permite consultar nombres de personas a través de la clase abstracta “Empleado”. Sin embargo, la primera letra de los nombres re­cu­pe­ra­dos siempre está en mi­nú­s­cu­las. Al ser imposible un ajuste en re­tro­s­pe­c­ti­va, se im­ple­me­n­ta la clase Decorador Em­plea­do­De­co­ra­tor, que opera en la misma interfaz y también permite llamar al método getName(). El decorador recibe una lógica que garantiza que la primera letra se escribe co­rre­c­ta­me­n­te en ma­yú­s­cu­las. En código, esto se escribe así:

public class EmpleadoDecorator implements Person {
private Empleado empleado;
public EmpleadoDecorator(Empleado empleado){
	this.empleado = empleado;
}
public String getName(){
	// llama al método de la clase empleado 
	String name = empleado.getName();
	// Asegúrate de que la primera letra está en mayúsculas
	name = Character.toUpperCase(name.charAt(0)) 
	+ name.substring(1, name.length());
	return name;
}
}
Ir al menú principal