Hola, soy Luis y aquí les traigo otro artículo.
El patrón de decorador es uno de los patrones de diseño más versátiles que tenemos disponibles en ingeniería de software. Permite que el comportamiento se agregue a un objeto de forma dinámica sin dejar de obedecer el principio de responsabilidad única. Es similar al patrón de Cadena de responsabilidad, sobre el que escribí aquí, pero una diferencia clave es que en lugar de una sola clase (o enlace en la cadena) que maneja una solicitud o acción en particular, todas las clases manejan la solicitud o acción.
Este artículo muestra lo útil que puede ser esto al transformar datos, generalmente como parte de una canalización de extracción, transformación y carga. En el ejemplo, veremos cómo se puede aplicar el patrón de decorador para extender la funcionalidad de un objeto mientras se mantiene el cumplimiento del principio de responsabilidad única.
Ok, sigamos adelante.
En este ejemplo, imagine un sistema que almacena y procesa datos de productos básicos: datos sobre petróleo, oro, plata, etc. Específicamente, veremos un objeto de transferencia de datos, un DTO, que contiene datos recuperados de la base de datos.
Esta clase, Mercancía, almacena el nombre de la mercancía y algunos datos de series de tiempo en un diccionario.
Nuestro sistema necesita procesar instancias de este modelo y producir una salida en JSON. Bastante simple ¿eh?
Bueno, casi…
Verá que la salida debe incluir valores para ValueAtEndOfYear durante años en el futuro. En algún lugar de nuestra cartera, tendremos que hacer algunos cálculos y crear tendencias en algunos valores nuevos.
Aquí es donde puede brillar el patrón del decorador.
Antes de continuar, hagamos la pregunta «¿podemos poner la funcionalidad para hacer esas matemáticas en la clase modelo?»
La respuesta corta es un enfático «No».
La respuesta más larga es: sí, puedes. Pero no querrías hacerlo. Hacer esto violaría el principio de responsabilidad única (la responsabilidad de los productos básicos es transferir datos y nada más) e introducir una funcionalidad que cambiaría su propio estado de forma encapsulada y ofuscada. De esa forma se encuentran los errores y los resultados inesperados …
Bien, con eso fuera del camino, sigamos adelante y comencemos a implementar el patrón del decorador.
Primero, echemos un vistazo a la salida esperada: es un objeto JSON simple.
Ok, esto es obviamente diferente de la estructura del DTO, por lo que necesitaremos un decorador para hacer algunas transformaciones, junto con algunas matemáticas. No importa demasiado en qué dirección hagamos las cosas, pero parece tener sentido hacer las matemáticas primero. Entonces eso es lo que vamos a hacer.
Primero, necesitaremos una interfaz o una clase base. Estoy usando una interfaz en este ejemplo, ymmv, para algunas situaciones, una clase base puede ser más adecuada. Necesitamos esto porque nuestros decoradores necesitan implementar los mismos métodos que nuestros objetos decorados.
Aquí está nuestra interfaz y asumiremos que la hemos implementado en la clase Commodity.
Ahora necesitamos implementar una clase decoradora base. Esto abstrae la funcionalidad común. Al hacer esto de verdad, asegúrese de mantener su enfoque estrecho, no quiere terminar con un plátano, gorila, problema de la jungla!
Para nuestro pequeño ejemplo, esta clase base puede parecer un poco superflua (y lo es, para ser honesto) pero si estuvieras implementando esto de verdad, seguramente querrías hacer estallar una funcionalidad común en una clase base.
Ok, esa es la clase base hecha. Ahora implementemos un decorador que calcule los valores de tendencia.
Echemos un vistazo rápido a lo que está pasando aquí.
El constructor toma una instancia de ICommodity, que podría ser otro decorador o una instancia de la clase Commodity. También toma una instancia de ITrend. No entraremos en detalles sobre qué es esto, pero, esencialmente, realiza los cálculos matemáticos para determinar la tendencia de los valores.
La acción principal está ocurriendo en la propiedad ValueAtEndOfYear. Estamos anulando la implementación en la clase base (que a su vez solo obtiene / establece las propiedades del producto decorado). En lugar de simplemente devolver el valor de la clase base, llamamos al método TrendValues. Este método utiliza la instancia de ITrend para generar tendencias en los valores y luego devolverlos, junto con los valores almacenados en la clase base.
A la derecha, al segundo decorador. Este manejará la transformación de datos para que nuestra salida coincida con el esquema requerido.
En este decorador, anulamos el método ToJsonString. Pasamos una instancia de IFormatter y usamos esto para obtener una cadena en el formato correcto. Al igual que con ITrend anterior, estamos tratando esto como una caja negra para este ejemplo, solo asuma que funciona como quisiéramos;)
A tener en cuenta:rá que ambos decoradores tienen algo en común: anulan los métodos. Esto es fundamental para comprender qué y cómo estamos logrando lo que somos. Usamos las clases de decorador para proporcionar datos adicionales o manipular datos existentes, y lo hacemos proporcionando anulaciones de métodos existentes.
Ok, cosas buenas. Ahora tenemos nuestros dos decoradores, para crear tendencias de valores y crear la salida con el formato correcto, y todo lo que tenemos que hacer es conectarlo.
A continuación se muestra un fragmento de una clase que maneja la exportación. Lo repasaremos en un segundo.
El método que nos interesa aquí es ExportAsJson. Toma una instancia de Commodity como argumento, solo estamos interesados en pasar los DTO de este método para que no usemos una interfaz.
Luego, en la línea 10, creamos una instancia de la clase CommodityTrendDecorator y pasamos a este el producto para exportar y la implementación de ITrend (en este caso, estamos prediciendo que este producto aumentará exponencialmente en valor).
En la línea 11 creamos una instancia de CommodityFormatDecorator y pasamos esta nuestra instancia del decorador de tendencias y una instancia de IFormatter.
Finalmente, obtenemos el resultado deseado; por brevedad, no he mostrado lo que realmente hacemos con este JSON bien formateado.
A tener en cuenta:rá cuán similar se ve el código en el método ExportAsJson a una cadena de responsabilidad. Dije al principio que los patrones de decorador y cadena de responsabilidad eran similares, ¡y ahora puedes ver por qué!
La diferencia también es clara: para completar nuestra operación utilizamos todas las clases condecoradas, cada una realiza alguna acción y no ceden la responsabilidad a otra.
Conclusión
En este artículo, hemos visto cómo podemos agregar comportamiento a una clase, cómo podemos extender su funcionalidad, sin violar el principio de responsabilidad única.
Tomamos un DTO simple y proporcionamos una mutación de su estado de una manera controlada y manejable. También hemos implementado una forma de formatear esos datos para exportarlos sin tener que contaminar el DTO con muchos métodos extraños.
Este tipo de cosas es bastante común al que nos enfrentamos en nuestro día a día, y utilizar decoradores no es la única solución. Lo que hemos hecho aquí podría hacerse pasando el DTO a través de una cadena de responsabilidad, por ejemplo. Dicho esto, si combina el patrón de decorador con el patrón de estrategia, puede crear sistemas flexibles pero robustos.
Gracias por leer.
Añadir comentario