Patrón Decorator: ampliación dinámica de clases
Si quieres ampliar las funcionalidades de una clase en un software orientado a objetos, tienes dos opciones. La opción más fácil es implementar subclases que complementen a la clase base. Sin embargo, esta opción puede volverse confusa. Como alternativa, puedes usar una entidad decoradora 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 funcionamiento. Es más, el patrón Decorador funciona sin recurrir a jerarquías de herencia largas y de difícil comprensión.
A continuación, te explicaremos qué es exactamente un patrón de diseño Decorador y cuáles son sus ventajas y desventajas y mostraremos 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 estrategia de diseño revelada en 1994 para expandir la funcionalidad de las clases dinámicamente en el software de computación orientado a objetos. Según este patrón, cualquier objeto puede complementarse con un comportamiento deseado sin influir en las funcionalidades de otros objetos de la misma clase. Desde el punto de vista estructural, el patrón Decorador tiene mucho en común con el patrón Cadena de Responsabilidad (Chain of Responsibility), aunque, en contraste con este, con un procesador central, todas las clases reciben las peticiones.
El componente de software que hay que ampliar se “decora” con una o más clases decoradoras, que lo envuelven completamente, siguiendo el patrón de diseño Decorador. Cada decorador es del mismo tipo que el componente al que envuelve y, por lo tanto, tiene la misma interfaz. De esta manera, las llamadas de método entrantes pueden delegarse fácilmente al componente adjunto mientras lleva a cabo una funcionalidad. Las llamadas también pueden procesarse 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 componentes del software orientado a objetos sean más flexibles y fáciles de reutilizar. Con este fin, el enfoque permite a los desarrolladores añadir y eliminar las dependencias 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 alternativa al uso de subclases, las cuales pueden complementar a una clase, pero no permiten hacer ajustes durante el tiempo de ejecución.
Un componente de software puede ampliarse con un número cualquiera de clases decoradoras. Estas extensiones son invisibles, lo que significa que normalmente es difícil percibir si una clase real está precedida por clases adicionales.
El patrón Decorador como diagrama UML
El decorador, o las clases decorador (ConcreteDecorator), tiene la misma interfaz que el componente de software a decorar (ConcreteComponent) y es del mismo tipo. Estos son requisitos importantes 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 procesarlas. En este concepto, esta interfaz elemental, que es esencialmente una superclase abstracta, recibe el nombre de “componente”.
La interacción entre el componente básico y el decorador es fácilmente visible si se ilustra con un diagrama de clases UML. Hemos utilizado este lenguaje de modelado para la programación orientada a objetos en el diagrama UML a continuación para entender el patrón Decorator:
Pros y contras del patrón de diseño Decorador
Considerar el patrón de diseño Decorator al diseñar un programa es útil por varias razones. En primer lugar, utilizar la estructura Decorador conlleva un alto grado de flexibilidad: las funcionalidades de las clases pueden ampliarse durante la compilación y durante el tiempo de ejecución sin necesidad de recurrir a una jerarquía de clases basadas en la herencia. Esto mejora significativamente la legibilidad del código del programa.
Debido a que la funcionalidad se divide en varias clases decoradoras, el rendimiento del software puede incrementarse. Esto facilita la recuperación e iniciación de funciones específicas. Con una clase base compleja que proporciona permanentemente todas las funciones, esta opción de recursos optimizados no está disponible.
Sin embargo, desarrollar usando el patrón Decorador tiene también algunas desventajas. Con la introducción del patrón aumenta la complejidad del software de forma automática. La interfaz Decorador, en particular, suele contener mucho texto y términos nuevos, lo que aumenta su complejidad. Otra desventaja es el gran número de objetos Decorador, razón por la cual se recomienda una sistematización separada para evitar problemas de visualización al trabajar con subclases. Por último, las largas cadenas de llamadas de los objetos decorados (es decir, los componentes ampliados) suelen dificultar la detección de errores y por ende también el proceso de depuración.
Ventajas | Desventajas |
---|---|
Alto grado de flexibilidad | Alta complejidad del software (especialmente la interfaz Decorador) |
Ampliación de las funciones de las clases sin herencia | De difícil comprensión al principio |
Código de programa legible | Alto número de objetos |
Funcionalidades optimizadas para los recursos | Proceso de depuración difícil |
Patrón Decorator: ejemplos
El patrón Decorador proporciona la base para implementar objetos dinámicos, transparentes y expandibles en un programa. En particular, los componentes de las interfaces gráficas de usuario (GUI) figuran como áreas de aplicació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 implementación del patrón Decorador lo constituyen las llamadas clases stream de la biblioteca de Java, respondables de gestionar la entrada y salida de datos. Aquí, las clases Decorador se utilizan para añadir nuevas propiedades e información de estado al flujo de datos o para proporcionar nuevas interfaces.
Java no es el único lenguaje de programación que hace un uso generalizado del patrón Decorador. Los siguientes lenguajes de programación también lo utilizan:
- C++
- C#
- Go
- JavaScript
- Python
- PHP
Aplicació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 modificarse a posteriori, y especialmente 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 presentamos a continuació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 recuperados siempre está en minúsculas. Al ser imposible un ajuste en retrospectiva, se implementa la clase Decorador EmpleadoDecorator, 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 correctamente en mayúsculas. 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;
}
}