Menu
Coddy logo textTech

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 fuerte entre 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 dicho 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 le das 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 Perro es un Animal), mientras que la composición representa "tiene-un" (un Personaje tiene un comportamiento de movimiento). La composición te permite intercambiar comportamientos en tiempo de ejecución, combinar capacidades libremente y evitar el problema de la clase base frágil.

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 de forma dinámica.

challenge icon

Desafío

Fácil

Vamos a construir un sistema de notificación que demuestre el poder de la composición sobre la herencia. En lugar de crear una jerarquía compleja de clases de notificación, compondrás un NotificationSender dándole componentes de comportamiento intercambiables.

Organizarás tu código en tres archivos:

  • Behaviors.cs: Crea dos clases de comportamiento en el espacio de nombres Notifications que manejen diferentes aspectos del envío de notificaciones:
    • FormattingBehavior - tiene un método Format(string message) que devuelve el mensaje envuelto con [ALERT] al inicio y [END] al final
    • DeliveryBehavior - tiene un método Deliver(string channel, string message) que devuelve una cadena en el formato Sending via {channel}: {message}
  • NotificationSender.cs: Crea una clase NotificationSender en el espacio de nombres Notifications que utilice la composición para combinar comportamientos. Tu remitente debe:
    • Contener instancias tanto de FormattingBehavior como de DeliveryBehavior como 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 formatee el mensaje usando el comportamiento de formateo, luego lo entregue usando el comportamiento de entrega y devuelva el resultado final
  • Program.cs: En tu archivo principal, crea un NotificationSender con un canal proveniente de la entrada, luego envía un mensaje proveniente de la entrada. Imprime el resultado para mostrar cómo los comportamientos compuestos trabajan juntos.

Recibirás dos entradas:

  • El nombre del canal de entrega (por ejemplo, Email, SMS)
  • El mensaje a enviar

Imprime el resultado de llamar a Send() en tu remitente 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 cómo el NotificationSender no hereda de nada; simplemente "tiene" comportamientos de formateo y entrega. ¡Este enfoque de composición significa que podrías intercambiar fácilmente cualquiera de los comportamientos sin cambiar la clase remitente, o reutilizar estos comportamientos en clases completamente diferentes!

Hoja de referencia

La composición es una alternativa a la herencia que crea código más flexible al hacer que una clase contenga objetos que proporcionan comportamiento, en lugar de heredar el comportamiento de una clase padre.

Herencia vs Composición:

  • La herencia representa relaciones de tipo "es-un" (un Perro es un Animal)
  • La composición representa relaciones de tipo "tiene-un" (un Personaje tiene un comportamiento de movimiento)

Beneficios de la Composición:

  • Intercambiar comportamientos en tiempo de ejecución
  • Combinar capacidades libremente
  • Evitar el acoplamiento fuerte y los problemas de clase base frágil
  • Compartir comportamientos entre clases no relacionadas

Ejemplo con componentes de comportamiento:

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();
}

Cuándo usar cada enfoque:

  • Usa la herencia para relaciones jerárquicas genuinas
  • Elige la composición para obtener flexibilidad, comportamientos compartidos o cambios de comportamiento dinámicos

Pruébalo tú mismo

using System;
using Notifications;

class Program
{
    public static void Main(string[] args)
    {
        // Leer entrada
        string channel = Console.ReadLine();
        string message = Console.ReadLine();

        // TODO: Crear un NotificationSender con el canal
        
        // TODO: Enviar el mensaje e imprimir el resultado
        
    }
}
quiz iconPonte a prueba

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