Composición sobre herencia
Parte de la sección Programación Orientada a Objetos del Journey de C# de Coddy. Lección 41 de 70.
La herencia crea un acoplamiento estrecho entre las clases. Cuando Car hereda de Vehicle, cualquier cambio en Vehicle puede romper Car. La composición ofrece una alternativa más flexible: en lugar de heredar el comportamiento, una clase contiene objetos que proporcionan ese comportamiento.
Considera un personaje de juego que puede moverse y atacar. Con la herencia, podrías crear una jerarquía compleja. Con la composición, simplemente proporcionas al personaje los componentes que necesita:
public class MovementBehavior
{
public void Move() => Console.WriteLine("Moving...");
}
public class AttackBehavior
{
public void Attack() => Console.WriteLine("Attacking!");
}
public class Character
{
private MovementBehavior movement = new MovementBehavior();
private AttackBehavior attack = new AttackBehavior();
public void Move() => movement.Move();
public void Attack() => attack.Attack();
}La diferencia clave es la relación. La herencia representa «es-un» (un Dog es un Animal), mientras que la composición representa «tiene-un» (un Character tiene un comportamiento de movimiento). La composición te permite intercambiar comportamientos en tiempo de ejecución, combinar capacidades libremente y evitar el frágil problema de la clase base.
Usa la herencia cuando exista una relación jerárquica genuina. Elige la composición cuando necesites flexibilidad, cuando los comportamientos puedan compartirse entre clases no relacionadas o cuando quieras cambiar el comportamiento dinámicamente.
Desafío
FácilConstruyamos un sistema de notificaciones que demuestre el poder de la composición frente a la herencia. En lugar de crear una jerarquía compleja de clases de notificación, compondrás un NotificationSender proporcionándole componentes de comportamiento intercambiables.
Organizarás tu código en tres archivos:
Behaviors.cs: crea dos clases de comportamiento en el espacio de nombresNotificationsque gestionen distintos aspectos del envío de notificaciones:FormattingBehavior: tiene un métodoFormat(string message)que devuelve el mensaje envuelto con[ALERT]al principio y[END]al finalDeliveryBehavior: tiene un métodoDeliver(string channel, string message)que devuelve una cadena con el formatoSending via {channel}: {message}
NotificationSender.cs: crea una claseNotificationSenderen el espacio de nombresNotificationsque use la composición para combinar comportamientos. Tu emisor debe:- Contener instancias de
FormattingBehavioryDeliveryBehaviorcomo campos privados - Tener una propiedad
Channel(string) que almacene el nombre del canal de entrega - Tener un constructor que acepte el nombre del canal
- Tener un método
Send(string message)que primero dé formato al mensaje usando el comportamiento de formato, después lo entregue usando el comportamiento de entrega y devuelva el resultado final
- Contener instancias de
Program.cs: en tu archivo principal, crea unNotificationSendercon un canal obtenido de la entrada y después envía un mensaje obtenido de la entrada. Imprime el resultado para mostrar cómo funcionan conjuntamente los comportamientos compuestos.
Recibirás dos entradas:
- El nombre del canal de entrega (por ejemplo,
Email,SMS) - El mensaje que se enviará
Imprime el resultado de llamar a Send() en tu emisor de notificaciones.
Por ejemplo, si las entradas son Email y Server is down, la salida debería ser:
Sending via Email: [ALERT] Server is down [END]Observa que NotificationSender no hereda de nada: simplemente «tiene» comportamientos de formato y entrega. Este enfoque de composición significa que podrías intercambiar fácilmente cualquiera de los dos comportamientos sin cambiar la clase emisora, ¡o reutilizar estos comportamientos en clases completamente diferentes!
Pruébalo tú mismo
using System;
using Notifications;
class Program
{
public static void Main(string[] args)
{
// Lee la entrada
string channel = Console.ReadLine();
string message = Console.ReadLine();
// TODO: Crea un NotificationSender con el canal
// TODO: Envía el mensaje e imprime el resultado
}
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación Orientada a Objetos
1Fundamentos de OOP
Archivos externosNamespaces y directivasIntro a clases y objetosLa palabra clave 'this'Métodos y parámetrosCampos vs PropiedadesConstructoresInicializadores de objetosResumen - Calculadora simple4Herencia
Sintaxis básica de herencia (:)La palabra clave 'base'Palabras clave Virtual y OverrideClases selladas (Sealed)La clase base 'object'Resumen: Jerarquía de empleados7Características avanzadas
Sobrecarga de operadoresIndexadores (this[])Sobrescritura de ToString()Métodos de extensiónResumen - Lista personalizada10Patrones de diseño - Parte 1
Introducción a los patrones de diseñoSingleton Thread-SafePatrón FactoryPatrón Observer (Eventos)Patrón Strategy2Propiedades y miembros estáticos
Propiedades autoimplementadasPropiedades de solo lectura y escrituraCampos y métodos estáticosClases estáticasMiembros con cuerpo de expresión5Polimorfismo e Interfaces
Polimorfismo: Compilación vs. EjecuciónInterfaz vs. Clase AbstractaInterfaces MúltiplesInterfaces ExplícitasUpcasting y DowncastingRepaso: Calculadora de Figuras8Conceptos avanzados de POO
Composición sobre herenciaGenéricos (clases y métodos)Delegados y eventosAtributos y ReflexiónIDisposable y la sentencia usingFundamentos de Inyección de dependencias11Patrones de diseño, parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón Composite3Arquitectura de clases
Datos de instancia vs. estáticosPalabras clave 'readonly' y 'const'Campos de respaldoResumen - Gestor de cuentas bancarias6Encapsulamiento
Modificadores de accesoPropiedades para el encapsulamientoImplementación de ocultamiento de datosPatrones de inmutabilidadRepaso - Registros de estudiantesPractica por tu cuenta: Compilador de C# online