C# Object oriented programming Composition vs Inheritance

Abhishek Malaviya
4 min readAug 25, 2023

--

Let’s dive into the two key players in object-oriented programming: Inheritance and Composition. In our daily development work, we often use both of these concepts based on what our specific needs are. Let’s explore these ideas to gain a deeper understanding.

Inheritance

Inheritance is not flexible. As we know, most languages do not allow you to derive from more than one type (multiple inheritance).

    class BaseClass1
{
public void BaseMethod1()
{

}
}

class BaseClass2
{
public void BaseMethod2()
{

}
}

class DerivedClass : BaseClass1, BaseClass2
{

}

This code will give compile time error.

Inheritance is compile time. We have already defined accessible base class methods and properties for the child classes.

Here, we can see The class DerivedClass inherits BaseMethod() from BaseClass. Which is defined at compile time or design time. It means the implementation inherited from the base class cannot be changed at runtime.

    class BaseClass
{
public void BaseMethod()
{
Console.WriteLine("Base Method");
}
}
class DerivedClass : BaseClass
{

}

//Run test logic
DerivedClass derivedClass = new DerivedClass();
derivedClass.BaseMethod();

Output

Consequences

Break encapsulation

When you inherit from a parent class, you gain access to its properties and methods, but you also inherit its implementation details, including its private and protected members. This is why it’s often said that inheritance can break encapsulation, which is one of the fundamental principles of object-oriented programming.

Tightly couple

Inheritance can lead to a subclass being tightly coupled to the implementation details of its parent class.

Any changes made to the implementation of the parent class can potentially ripple down to affect the behavior of the subclass. This can make code maintenance and evolution more challenging because changes to one part of the codebase can have unpredictable effects on other parts.

Composition

In Composition, we can change behavior at run-time.

interface IExample
{
void Method1();
}

class TestClass1 : IExample
{
public void Method1()
{
Console.WriteLine($"Method1 of class:{nameof(TestClass1)}");
}
}

class TestClass2 : IExample
{
public void Method1()
{
Console.WriteLine($"Method1 of class:{nameof(TestClass2)}");
}
}

class MyClass
{
private IExample _example;

public MyClass(IExample example)
{
_example = example;
}

public void ActualExample()
{
_example.Method1();
}

public void ChangeExample()
{
_example = new TestClass2();
_example.Method1();
}
}

//Run logic

MyClass myClass = new MyClass(new TestClass1());
myClass.ActualExample();
myClass.ChangeExample();

Output

runtime change behavior.

You can leverage Composition to adhere to the Single Responsibility Principle (SRP) when dealing with cross-cutting concerns. The SRP states that a class should have only one reason to change, meaning it should have a single responsibility.

Cross-cutting concerns, such as logging, security and error handling are often responsibilities that cut across multiple classes and modules in an application. Instead of scattering this code throughout various classes, which would violate the SRP, you can use Composition to encapsulate these concerns in separate classes or modules.

This way, each class has a clear and single responsibility, while the cross-cutting concerns are encapsulated in their own dedicated components. This makes your code more modular, maintainable, and aligns with the SRP.

Here Notification class has single responsibility to send message but if we want to introduce log message while sending notification, we can easily achieve using composition without violating single responbilty principle.

    class Notification
{
private readonly ILogger _logger;
public TestClass(ILogger logger)
{
_logger = logger;
}

public void Send()
{
//Some logic
//.....
//....
//....


_logger.LogMessage("Message");
}
}

interface ILogger
{
void LogMessage(string msg);
}

Use Case

When it comes to choosing between Inheritance and Composition, it depends on our specific use case:

  1. If our main concern is maximizing code reusability, then Inheritance is the way to go. Inheritance allows us to create new classes that inherit properties and behaviors from existing classes, promoting code reuse through the “is-a” relationship.
  2. However, if we need to change behavior dynamically at runtime or want more flexibility in combining and altering functionalities, Composition is the better choice. Composition involves creating objects that reference and use other objects, enabling us to modify behavior by changing the components or “parts” that make up an object.

In summary, for reusability, opt for Inheritance, and for runtime behavior changes and flexibility, go with Composition. The choice depends on the specific requirements of your project.

--

--