Integración de bibliotecas de terceros mediante el patrón Decorator

El patrón Decorator permite extender el comportamiento de un objeto envolviéndolo en otra clase que implementa la misma interfaz, sin alterar su código fuente. Esta técnica ofrece varias ventajas clave:

  • Acoplamiento reducido: El objeto envolvente y el envuelto pueden evolucionar de manera independiente.
  • Composición flexible: Es posible encadenar múltiples decoradores en tiempo de ejecución para construir distintas variantes de funcionalidad.
  • Evita jerarquías de herencia complejas: Al no depender de subclases, se mantiene el sistema más limpio y manejable.
  • Reutilización: Cada decorador encapsula una única responsabilidad y puede aplicarse a cualquier objeto que comparta la misma interfaz.
  • Cumplimiento del principio de abierto/cerrado: Se añaden capacidades sin modificar el código existente.

Conceptos fundamentales

  • Interfaz componente: Define el contrato común que comparten tanto el objeto original como los decoradores.
  • Componente concreto: Implementa la interfaz y representa el objeto base que se desea envolver.
  • Clase decoradora base: Implementa la interfaz y mantiene una referencia al objeto envuelto, delegando las llamadas en él.
  • Decoradores concretos: Heredan de la base y añaden lógica adicinoal antes o después de delegar la ejecución.

La interfaz y el componente concreto pueden provenir de una biblioteca externa o de código propio. El decorador actúa como intermediario: recibe las mismas llamadas que el objeto original, puede procesarlas y luego las reenvía al componente interno.

Ejemplo práctico: envolver una clase de transacciones

Supongamos que existe una clase externa con métodos para comprar y vender. Aplicaremos el patrón Decorator para añadir capacidades como registro de logs sin modificar dicha clase.

Primero, declaramos la interfaz común:

class ITransactionOps {
public:
    virtual ~ITransactionOps() = default;
    virtual void executePurchase() = 0;
    virtual void executeSale() = 0;
};

Luego, implementamos el componente concreto:

class StandardTransaction : public ITransactionOps {
public:
    void executePurchase() override {
        // Lógica de compra
    }

    void executeSale() override {
        // Lógica de venta
    }
};

Definimos la clase decoradora base que delega al objeto interno:

class TransactionWrapper : public ITransactionOps {
protected:
    ITransactionOps* inner;

public:
    TransactionWrapper(ITransactionOps* target) : inner(target) {}

    void executePurchase() override {
        inner->executePurchase();
    }

    void executeSale() override {
        inner->executeSale();
    }
};

Finalmente, creamos un decorador concreto que añade registro de actividad:

class AuditLogWrapper : public TransactionWrapper {
public:
    AuditLogWrapper(ITransactionOps* target) : TransactionWrapper(target) {}

    void executePurchase() override {
        // Registrar evento de compra
        TransactionWrapper::executePurchase();
    }

    void executeSale() override {
        // Registrar evento de venta
        TransactionWrapper::executeSale();
    }
};

Uso en el programa principal:

int main() {
    ITransactionOps* baseTx = new StandardTransaction();
    ITransactionOps* loggedTx = new AuditLogWrapper(baseTx);

    loggedTx->executePurchase();
    loggedTx->executeSale();

    delete loggedTx;
    delete baseTx;

    return 0;
}

Etiquetas: Decorator Pattern design patterns C++ Object-Oriented Design

Publicado el 10-7 18:38