15:01 0 0
Patrón Decorator

Patrón Decorator

  DrUalcman |  mayo 192023

El patrón de diseño Decorator se utiliza para agregar funcionalidad adicional a un objeto dinámicamente, sin modificar su estructura básica. Esto se logra envolviendo el objeto original en una serie de objetos decoradores que agregan o modifican su comportamiento. Pongamos un ejemplo para entenderlo mejor.

Imaginemos que tenemos una interfaz llamada ICuentaBancaria que representa una cuenta bancaria genérica con métodos como ObtenerSaldo y RealizarOperacion. A continuación, crearemos una implementación concreta llamada CuentaBancariaBase.

public interface ICuentaBancaria
{
decimal ObtenerSaldo();
void RealizarOperacion(decimal cantidad);
}

public class CuentaBancariaBase : ICuentaBancaria
{
private decimal saldo;

public decimal ObtenerSaldo()
{
return saldo;
}

public void RealizarOperacion(decimal cantidad)
{
saldo += cantidad;
}
}

Ahora, supongamos que queremos agregar funcionalidad adicional a esta cuenta bancaria, como la capacidad de aplicar un interés mensual. En lugar de modificar directamente la implementación CuentaBancariaBase, podemos utilizar el patrón Decorator.

Creamos una clase CuentaBancariaDecorator que implementa la interfaz ICuentaBancaria y también tiene un campo para almacenar una referencia a la cuenta bancaria original. Esta clase actuará como base para todos los decoradores.

public abstract class CuentaBancariaDecorator : ICuentaBancaria
{
protected ICuentaBancaria cuentaBancaria;

public CuentaBancariaDecorator(ICuentaBancaria cuentaBancaria)
{
this.cuentaBancaria = cuentaBancaria;
}

public virtual decimal ObtenerSaldo()
{
return cuentaBancaria.ObtenerSaldo();
}

public virtual void RealizarOperacion(decimal cantidad)
{
cuentaBancaria.RealizarOperacion(cantidad);
}
}

A continuación, creamos un decorador concreto llamado CuentaBancariaConInteres que agrega la funcionalidad de aplicar un interés mensual a la cuenta bancaria original.

public class CuentaBancariaConInteres : CuentaBancariaDecorator
{
private decimal tasaInteres;

public CuentaBancariaConInteres(ICuentaBancaria cuentaBancaria, decimal tasaInteres)
: base(cuentaBancaria)
{
this.tasaInteres = tasaInteres;
}

public override decimal ObtenerSaldo()
{
decimal saldoBase = base.ObtenerSaldo();
decimal saldoConInteres = saldoBase * (1 + tasaInteres);
return saldoConInteres;
}
}

Ahora podemos utilizar este decorador de la siguiente manera:

// Creamos una instancia de la cuenta bancaria base
ICuentaBancaria cuentaBase = new CuentaBancariaBase();

// Creamos una instancia del decorador con interés y envolvemos la cuenta base
ICuentaBancaria cuentaConInteres = new CuentaBancariaConInteres(cuentaBase, 0.05m);

// Realizamos operaciones en la cuenta con interés
cuentaConInteres.RealizarOperacion

Cuando utilizar decoradores

Utilizar ete patron pude tener ventajas significativas que pueden hacer que su uso sea beneficioso en determinadas situaciones. Aquí tienes algunas ventajas del patrón Decorator:

1. Flexibilidad y extensibilidad: El patrón Decorator permite agregar funcionalidad adicional de forma dinámica y flexible sin modificar la estructura de la clase base. Puedes envolver un objeto con diferentes combinaciones de decoradores para obtener diferentes comportamientos. Esto proporciona una gran flexibilidad y extensibilidad, ya que puedes añadir o quitar funcionalidad de forma individual sin afectar al resto del código.

2. Cumplimiento del principio de "abrierto/cerrado": El patrón Decorator es una forma de cumplir el principio SOLID de "abrierto/cerrado" (Open-Closed Principle). Te permite extender el comportamiento de un objeto sin tener que modificar el código existente. Esto significa que puedes agregar nuevas funcionalidades sin afectar el código ya probado y funcionando correctamente.

3. Separación de preocupaciones: El patrón Decorator permite separar las preocupaciones y responsabilidades en clases distintas. Cada decorador tiene una única responsabilidad y se encarga de agregar una funcionalidad específica. Esto facilita el mantenimiento del código y mejora su legibilidad, ya que cada decorador se centra en una tarea concreta.

4. Composición de funcionalidades: Con el patrón Decorator, puedes combinar diferentes decoradores para crear composiciones complejas de funcionalidades. Puedes agregar múltiples decoradores a un objeto base, cada uno con su propia funcionalidad adicional, y obtener así una combinación única de características.

5. Reutilización de código: Al utilizar el patrón Decorator, puedes reutilizar los decoradores existentes para agregar funcionalidad a diferentes objetos. Esto promueve la reutilización de código y evita la duplicación, ya que los decoradores pueden aplicarse a múltiples objetos en diferentes contextos.

Ahora el lado malo. Cuando no utilizar decoradores

El patrón Decorator, como cualquier otro patrón de diseño, tiene sus ventajas y desventajas. A continuación, te mencionaré algunos inconvenientes que podrían surgir al utilizar este patrón:

1. Aumento de la complejidad: El uso del patrón Decorator puede aumentar la complejidad del código, ya que implica la creación de múltiples clases y su interconexión. Esto puede dificultar la comprensión y el mantenimiento del código, especialmente cuando se tienen muchos decoradores y combinaciones posibles.

2. Posible sobrecarga de objetos: Al utilizar decoradores, se envuelve el objeto original con capas adicionales que agregan funcionalidad. Esto puede resultar en un aumento de la sobrecarga de objetos, ya que cada decorador agrega su propia lógica y datos adicionales. Si se utilizan muchos decoradores anidados, podría generarse una gran cantidad de objetos en memoria.

3. Dificultad para eliminar o cambiar la funcionalidad decorada: En algunos casos, puede resultar difícil eliminar o cambiar la funcionalidad decorada en tiempo de ejecución. Debido a la naturaleza dinámica del patrón Decorator, la eliminación selectiva de decoradores o la modificación de la funcionalidad puede requerir un diseño cuidadoso y una gestión adecuada de los objetos decoradores.

4. Posible impacto en el rendimiento: El uso excesivo de decoradores puede tener un impacto en el rendimiento del sistema debido a la sobrecarga adicional de procesamiento y memoria. Cada llamada a un método decorado implica una cadena de llamadas a través de los decoradores, lo cual puede tener un costo computacional.

Es importante evaluar cuidadosamente los pros y los contras del patrón Decorator antes de utilizarlo en un proyecto, y considerar si su aplicación es adecuada para el caso específico.

Usalo junto con estos patrones sin problemas

El patrón Decorator se puede combinar de manera efectiva con otros patrones de diseño para obtener soluciones más complejas y flexibles. Aquí tienes algunos patrones que podrían complementarse bien con el patrón Decorator:

1. Patrón Composite: El patrón Composite se utiliza para representar jerarquías de objetos en estructuras de árbol. Puedes combinar el patrón Decorator con el patrón Composite para agregar funcionalidad adicional a objetos individuales dentro de la estructura del árbol sin afectar a otros objetos en la jerarquía.

2. Patrón Strategy: El patrón Strategy se utiliza para encapsular algoritmos intercambiables. Puedes utilizar el patrón Decorator junto con el patrón Strategy para agregar diferentes estrategias o comportamientos a un objeto utilizando decoradores. Esto permite que el objeto base cambie su comportamiento en tiempo de ejecución de acuerdo con la estrategia seleccionada.

3. Patrón Factory: El patrón Factory se utiliza para encapsular la creación de objetos. Puedes utilizar el patrón Decorator en combinación con el patrón Factory para crear objetos decorados de manera más flexible. La fábrica puede devolver objetos base o decorados según las necesidades del cliente.

4. Patrón Chain of Responsibility: El patrón Chain of Responsibility se utiliza para establecer una cadena de objetos receptores que pueden procesar una solicitud. Puedes combinar el patrón Decorator con el patrón Chain of Responsibility para decorar objetos en la cadena de responsabilidad. Cada decorador puede agregar funcionalidad adicional a la solicitud antes de pasarla al siguiente objeto en la cadena.

Estas son solo algunas sugerencias de patrones de diseño que pueden combinarse con el patrón Decorator. La elección de qué patrón utilizar depende del problema específico que estés abordando y los requisitos de diseño de tu proyecto. Siempre es importante evaluar cuidadosamente el contexto y seleccionar los patrones de diseño adecuados para lograr una solución efectiva y mantenible.

Mejor no utilizarlo en conjunto con estos otros patrones

Hay algunos patrones de diseño en los que el uso del patrón Decorator puede no ser recomendable o no tener mucho sentido. Aquí tienes algunos patrones en los que el uso del patrón Decorator puede no ser apropiado:

1. Patrón Singleton: El patrón Singleton se utiliza para garantizar que solo haya una instancia de una clase en todo el sistema. En este caso, el patrón Decorator puede no ser adecuado, ya que envolver el objeto Singleton con decoradores podría introducir complejidad innecesaria y violar el principio de tener una única instancia.

2. Patrón Builder: El patrón Builder se utiliza para crear objetos complejos paso a paso. Si estás utilizando el patrón Builder, el patrón Decorator puede no ser necesario, ya que el Builder ya permite agregar y configurar diferentes características y propiedades al objeto construido.

3. Patrón Proxy: El patrón Proxy se utiliza para proporcionar un sustituto o representante de un objeto real. En este caso, el uso del patrón Decorator puede no ser apropiado, ya que el Proxy se utiliza principalmente para controlar el acceso al objeto real y no para agregar funcionalidad adicional.

4. Patrón Template Method: El patrón Template Method define la estructura de un algoritmo en una clase base y permite que las subclases implementen ciertos pasos del algoritmo. Si estás utilizando el patrón Template Method, el uso del patrón Decorator puede no ser necesario, ya que puedes extender y personalizar el algoritmo en las subclases sin necesidad de decoradores.

Recuerda que estas recomendaciones son generales y no se aplican a todas las situaciones. Siempre debes evaluar cuidadosamente el contexto y los requisitos específicos de tu proyecto antes de decidir qué patrones de diseño utilizar y cómo combinarlos.

Cuando es mejor no utlizar decoradores

Si bien el patrón Decorator puede ser útil en muchos casos, hay situaciones en las que puede no ser apropiado o innecesario. Aquí tienes algunas consideraciones sobre cuándo no utilizar el patrón Decorator:

1. Cuando la funcionalidad adicional es estática: Si la funcionalidad adicional que deseas agregar a un objeto es estática y no cambia durante el tiempo de ejecución, no es necesario utilizar el patrón Decorator. En cambio, puedes agregar directamente la funcionalidad en la implementación original del objeto sin la necesidad de envolverlo en decoradores.

2. Cuando la estructura del objeto es compleja: Si el objeto base tiene una estructura compleja y contiene muchos subobjetos anidados, el uso del patrón Decorator puede aumentar aún más la complejidad. En este caso, considera otras opciones de diseño que puedan simplificar la estructura y evitar la necesidad de decoradores.

3. Cuando la funcionalidad adicional es conocida de antemano: Si la funcionalidad adicional que deseas agregar al objeto es conocida y limitada de antemano, puedes optar por utilizar herencia en lugar del patrón Decorator. La herencia te permite definir subclases que extiendan la funcionalidad base de forma directa y estática.

4. Cuando la sobrecarga de objetos es un problema: Si tienes restricciones de memoria o rendimiento y agregar decoradores implica una sobrecarga significativa de objetos, es posible que desees considerar otras alternativas más eficientes.

Conclusiones

Como puedes ver clarmente, este patrón esta muy casado a un programacion muy abstracta y con una analisis muy bueno de los objetos del programa que se crea. Pero puede ser de gran utilizadad en refactorizaciones de sistemas, ya que sencillamente hay que crer la interfaz ue abtrae el objeto a decorar y luego ya hacer la herencia y envolverlo en otros objetos que son los que van a agregar la funcionalidad deseada.


Hapy coding

0 Comentarios

 
 
 

Archivo