Menu
Coddy logo textTech

Composition over Inheritance

Part of the Object Oriented Programming section of Coddy's C# journey — lesson 41 of 70.

Inheritance creates tight coupling between classes. When a Car inherits from Vehicle, any change to Vehicle can break Car. Composition offers a more flexible alternative: instead of inheriting behavior, a class contains objects that provide that behavior.

Consider a game character that can move and attack. With inheritance, you might create a complex hierarchy. With composition, you simply give the character the components it needs:

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

The key difference is the relationship. Inheritance represents "is-a" (a Dog is an Animal), while composition represents "has-a" (a Character has movement behavior). Composition lets you swap behaviors at runtime, combine capabilities freely, and avoid the fragile base class problem.

Use inheritance when there's a genuine hierarchical relationship. Choose composition when you need flexibility, when behaviors might be shared across unrelated classes, or when you want to change behavior dynamically.

challenge icon

Challenge

Easy

Let's build a notification system that demonstrates the power of composition over inheritance. Instead of creating a complex hierarchy of notification classes, you'll compose a NotificationSender by giving it interchangeable behavior components.

You'll organize your code across three files:

  • Behaviors.cs: Create two behavior classes in the Notifications namespace that handle different aspects of sending notifications:
    • FormattingBehavior - has a method Format(string message) that returns the message wrapped with [ALERT] at the start and [END] at the end
    • DeliveryBehavior - has a method Deliver(string channel, string message) that returns a string in the format Sending via {channel}: {message}
  • NotificationSender.cs: Create a NotificationSender class in the Notifications namespace that uses composition to combine behaviors. Your sender should:
    • Contain instances of both FormattingBehavior and DeliveryBehavior as private fields
    • Have a Channel property (string) that stores the delivery channel name
    • Have a constructor that accepts the channel name
    • Have a Send(string message) method that first formats the message using the formatting behavior, then delivers it using the delivery behavior, and returns the final result
  • Program.cs: In your main file, create a NotificationSender with a channel from input, then send a message from input. Print the result to show how the composed behaviors work together.

You will receive two inputs:

  • The delivery channel name (e.g., Email, SMS)
  • The message to send

Print the result of calling Send() on your notification sender.

For example, if the inputs are Email and Server is down, the output should be:

Sending via Email: [ALERT] Server is down [END]

Notice how the NotificationSender doesn't inherit from anything - it simply "has" formatting and delivery behaviors. This composition approach means you could easily swap out either behavior without changing the sender class, or reuse these behaviors in completely different classes!

Cheat sheet

Composition is an alternative to inheritance that creates more flexible code by having a class contain objects that provide behavior, rather than inheriting behavior from a parent class.

Inheritance vs Composition:

  • Inheritance represents "is-a" relationships (a Dog is an Animal)
  • Composition represents "has-a" relationships (a Character has movement behavior)

Benefits of Composition:

  • Swap behaviors at runtime
  • Combine capabilities freely
  • Avoid tight coupling and fragile base class problems
  • Share behaviors across unrelated classes

Example with behavior components:

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

When to use each approach:

  • Use inheritance for genuine hierarchical relationships
  • Choose composition for flexibility, shared behaviors, or dynamic behavior changes

Try it yourself

using System;
using Notifications;

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

        // TODO: Create a NotificationSender with the channel
        
        // TODO: Send the message and print the result
        
    }
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming