La delegación de responsabilidades a través de la composición es una práctica de programación en la que un objeto de una clase contiene un objeto de otra clase (o clases) y los utiliza para implementar parte de su lógica, en lugar de heredar su interfaz.
Las primeras versiones de OOP se centraron en la herencia, pero con el tiempo la práctica ha demostrado que la composición a menudo proporciona mayor flexibilidad, escalabilidad y reduce el acoplamiento entre componentes.
La herencia vincula objetos de manera rígida: los cambios en la clase base afectan a todos los descendientes, las jerarquías se vuelven complejas y surge la fragilidad de la arquitectura. La composición elimina estos problemas, permitiendo construir sistemas más confiables y mantenibles.
En C++, la delegación se implementa mediante la inclusión de un objeto de una clase como miembro de otra clase. En la clase envolvente, se llaman a los métodos del objeto anidado.
Ejemplo de código:
class Logger { public: void log(const std::string& msg) { std::cout << msg << std::endl; } }; class FileProcessor { Logger logger; // Composición public: void process(const std::string& filename) { logger.log("Processing file: " + filename); // ... } };
Características clave:
¿Puede la composición reemplazar completamente la herencia?
No, la herencia es necesaria donde se requiere una relación "es un" (is-a), mientras que la composición es cuando "tiene" (has-a). Por ejemplo, Button hereda de Widget, pero Car "tiene" Engine (composición).
¿Se puede cambiar el comportamiento del método delegado en composición?
Sí, los métodos de delegación se pueden adaptar sin tocar la clase original. También se puede cambiar dinámicamente el objeto delegado (por ejemplo, a través de un puntero o puntero único).
¿Es la composición más lenta que la herencia?
No, en la mayoría de los casos no hay diferencia de rendimiento. A veces, la herencia agrega costos por llamadas virtuales (vtable), mientras que la composición solo aumenta el tamaño del objeto.
En el proyecto, todas las ventanas de diálogo heredaban de un DialogWindow común. La adición de nueva lógica de negocio conducía a código no funcional en todos los descendientes.
Ventajas:
Desventajas:
Las funciones comunes se extrajeron en clases separadas (registro, validación), que se inyectan en cada diálogo a través de la composición.
Ventajas:
Desventajas: