Delegación de comportamiento mediante interfaces
Una de las formas más clásicas y efectivas de conseguir comportamiento intercambiable en la programación orientada a objetos es la delegación mediante interfaces. En este enfoque, una clase no implementa directamente un comportamiento, sino que lo delega a otro objeto que cumple una interfaz abstracta.
Gracias a este principio, es posible sustituir libremente las implementaciones concretas sin modificar el código cliente, logrando un sistema más flexible, extensible y fácilmente comprobable.
Este concepto se fundamenta en uno de los pilares del diseño orientado a objetos: “Programa contra interfaces, no contra implementaciones.”
Ejemplo completo: delegación de comportamiento
En el siguiente ejemplo, un objeto Ordenador delega la tarea de ordenar datos a una estrategia que implementa una interfaz común.
#include <iostream>
#include <vector>
#include <algorithm>
#include <memory>
// ---------------------------------------------------------
// Interfaz abstracta: define el contrato del comportamiento
// ---------------------------------------------------------
class EstrategiaOrdenamiento {
public:
// Método virtual puro: cada clase derivada define su propio algoritmo
virtual void ordenar(std::vector<int>& datos) = 0;
// Destructor virtual para garantizar destrucción correcta
virtual ~EstrategiaOrdenamiento() = default;
};
// ---------------------------------------------------------
// Clase que delega el comportamiento de ordenación
// ---------------------------------------------------------
class Ordenador {
private:
std::unique_ptr<EstrategiaOrdenamiento> estrategia_; // Comportamiento inyectado
public:
// Constructor: recibe la estrategia a utilizar
explicit Ordenador(std::unique_ptr<EstrategiaOrdenamiento> estrategia)
: estrategia_(std::move(estrategia)) {}
// Método público que delega la tarea
void ordenar(std::vector<int>& datos) {
estrategia_->ordenar(datos); // Llamada polimórfica
}
};
// ---------------------------------------------------------
// Implementaciones concretas de la interfaz
// ---------------------------------------------------------
// Estrategia 1: ordenamiento burbuja (ineficiente, pero ilustrativo)
class OrdenamientoBurbuja : public EstrategiaOrdenamiento {
public:
void ordenar(std::vector<int>& datos) override {
for (size_t i = 0; i < datos.size(); ++i) {
for (size_t j = 0; j < datos.size() - 1; ++j) {
if (datos[j] > datos[j + 1]) {
std::swap(datos[j], datos[j + 1]);
}
}
}
std::cout << "Ordenado con burbuja\n";
}
};
// Estrategia 2: uso de std::sort (más eficiente)
class OrdenamientoRapido : public EstrategiaOrdenamiento {
public:
void ordenar(std::vector<int>& datos) override {
std::sort(datos.begin(), datos.end());
std::cout << "Ordenado con std::sort\n";
}
};
// ---------------------------------------------------------
// Programa principal: selección de comportamiento
// ---------------------------------------------------------
int main() {
std::vector<int> datos = {5, 2, 8, 1, 4};
// Primer ordenador usa la estrategia de burbuja
Ordenador ordenador1(std::make_unique<OrdenamientoBurbuja>());
ordenador1.ordenar(datos);
std::cout << "Resultado: ";
for (int n : datos)
std::cout << n << " ";
std::cout << "\n\n";
// Segundo ordenador usa la estrategia rápida
std::vector<int> otrosDatos = {9, 3, 7, 1, 6};
Ordenador ordenador2(std::make_unique<OrdenamientoRapido>());
ordenador2.ordenar(otrosDatos);
std::cout << "Resultado: ";
for (int n : otrosDatos)
std::cout << n << " ";
std::cout << "\n";
return 0;
}
EstrategiaOrdenamientodefine una interfaz pura con un único método virtual puroordenar(). No implementa el algoritmo, solo especifica el contrato.Ordenadorno contiene lógica de ordenación. Recibe la estrategia en su constructor y delega la tarea llamando aestrategia_->ordenar(datos).- La palabra clave
explicitevita que el compilador realice conversiones implícitas no deseadas desde unstd::unique_ptrhaciaOrdenador, garantizando que el objeto se construya solo de forma intencionada y explícita. OrdenamientoBurbujayOrdenamientoRapidoimplementan la interfaz de forma distinta. Ambas clases pueden usarse indistintamente enOrdenador.std::unique_ptrgestiona la memoria automáticamente, garantizando la destrucción correcta de las estrategias cuando el objetoOrdenadordeja de existir.- En el
main(), se crean dos ordenadores con comportamientos diferentes sin cambiar el código de la claseOrdenador.