Composition and Inheritance are programming techniques to establish relationships between different classes.

When working with an object-oriented language like C#, it is natural that we come across classes and objects. These classes, more than often, need to interact with each other if we are creating any meaningful application. Both composition and inheritance are tools to implement these interactions.

To download the source code for this article, you can visit our GitHub repository.

Let’s begin.

Inheritance

Inheritance is one of the critical characteristics of object-oriented programming. It is a mechanism of a class deriving its implementation from a base class. To put it simply, when a derived class inherits from a base class, it acquires properties and behaviors of the base class. 

Some of the most common types of inheritance are:

  • Single inheritance, where the derived class has a single base class
  • Hierarchical inheritance, where a base class has multiple derived classes
  • Multi-level inheritance, where a derived class acts as a base for another derived class

C# does not allow multiple class inheritance. We overcome this by using interfaces.

We use inheritance to achieve an is-a relationship between classes.

To see how inheritance works, let’s start by creating a base House class:

public class House
{
    public string Color { get; set; }
    
    public string GetAddress() => "Address";
}

Now, let’s create a BrickHouse class that inherits from the House class:

public class BrickHouse : House { }

And a new GlassHouse class:

public class GlassHouse : House
{
    public string WarningSign() => "No rocks please!";
}

Finally, we can modify the Program class:

var house1 = new GlassHouse();
house1.Color = "Transparent";
house1.GetAddress();
house1.WarningSign();

var house2 = new BrickHouse();
house2.Color = "Red";
house2.GetAddress();

We have two derived (or child) classes, GlassHouse and BrickHouse. The properties and methods of the base (or parent) class are accessible from the objects of derived classes.  These classes can also have additional properties and methods like the WarningSign() method in GlassHouse

Composition

We use composition to create a has-a relationship between classes.

A combination of component objects creates a composite object. Here, the composite class contains the object of component classes as members. In other words, every composite has a component.

Let’s continue with the example we used earlier. A House can be seen as a combination of several components like Ceiling, Floor, etc: 

public class House
{
    private readonly Ceiling _ceiling;
    private readonly Floor _floor;
    
    public House()
    {
        _ceiling = new Ceiling();
        _floor = new Floor();
    }

    public string GetCeiling() => _ceiling.BuildCeiling();

    public string GetFloor() => _floor.BuildFloor();
}

Here, we can represent the composition of a House using Ceiling and Floor class references.

With composition, we can choose the functionalities we want to implement in the composite class based on the components unlike inheritance, where the derived class automatically has access to all the base class methods.

What Is the Difference Between Inheritance and Composition?

Inheritance is all about the specialization of a general concept. The derived class is a specialized version of the base class and promotes code reuse. It implicitly inherits all non-private members of its base classes, whether direct or indirect. It can also hide or override the inherited members.

On the other hand, the composition is about the association of objects of different classes. It enables code reuse by adding a reference to another object instead of inheriting the complete implementation.

With inheritance, we get a tight coupling of code, and changes in the base class ripple down the hierarchy to derived classes. Whereas, a coupling created through composition is a loose one. It helps us achieve greater flexibility. We can add another component to accommodate any future change instead of restructuring the inheritance hierarchy.

One of the major advantages of inheritance is that we implicitly get all the base class methods in the derived class and there is no extra performance cost of invocation.

On the contrary, with composition, we are creating a composite object by plugging in the component objects. These component objects should not be exposed directly as they exist only within the composite context. We need to have a corresponding implementation of the component class methods in the composite class like GetCeiling() and GetFloor() in the House class to invoke BuildCeiling() and BuildFloor() methods respectively. This delegation of the method invocation may have a performance cost in addition to having to write extra code. 

Composition Over Inheritance

So, we have established that both composition and inheritance, are essential object-oriented programming techniques. Both of them promote code reuse through different approaches. However, in object-oriented design, we often hear the advice to prefer composition over inheritance to achieve polymorphism and code reuse. What is that about?

The main problem with inheritance is that it may lead to a deep hierarchy of classes. This hierarchy is fragile, and the implementation of derived classes can break or be forced to change with any change at the top of the hierarchy.

Let’s continue with our House example and say we want to introduce another type of House which is a Caravan. This leads to a problem because a caravan doesn’t have a fixed address. The hierarchy now needs to change, and introduce for example MovableHouse and FixedHouse classes.

However,  it’s not viable to re-write the complete code on introducing a change once it’s deployed.

This also leads to another common problem we face with inheritance. A derived class is exposed to all the functionalities of its base class. While good in terms of code reuse, this isn’t always beneficial as the derived class might not need all the functionalities.

For example, we can fix the problem of introducing a Caravan class by making the GetAddress() method as a virtual one in the House class:

public class House
{
    public virtual string GetAddress() => "Address";   
}

Now, we can modify the Caravan class:

public class Caravan : House
{
    public override string GetAddress() => throw new Exception("No fixed address");
}

As we don’t want our Caravan class to have a fixed address, we can override the GetAddress() method and throw an exception.

This takes care of our fragile base class problem at the moment. However, this isn’t ideal. The Caravan class should not have access to the functionality it does not support.

We can address this problem by using composition. We can treat Address as a separate component and move the GetAddress() method there:

public class Address
{
    public string GetAddress() => "Address";
}

Now, the BrickHouse class can have Address as its component:

public class BrickHouse : House
{
    private readonly Address _address;

    public BrickHouse()
    {
        _address = new Address();
    }

    public string GetAddress() => _address.GetAddress();
}

Whereas the Caravan class does not need to implement the same:

public class Caravan : House { }

Is Composition Better Than Inheritance?

This question doesn’t have a clear yes-no answer like everything else in software design. We need to understand the trade-offs when we make our decisions. “Composition over inheritance” is, after all, a guideline that we need to consider and does not mean we choose composition as a silver bullet.

For example, with inheritance, we get straightforward code reuse. i.e, the derived class can use the methods from the base class. Whereas, with composition, we need to re-write the code for every method in the component, even if just for delegation. It’s also easier in inheritance to extend or modify a base functionality by overriding it in the derived class.

Nevertheless, it is often preferable to use composition over inheritance. Even when we can use inheritance, it doesn’t mean we should use inheritance.

One test to decide whether we should use inheritance is establishing an “is-a” relationship between the classes. If  X is Y, class X can inherit from class Y.

This test alone might not be helpful every time. It may lead to the classic Circle-Ellipse problem where a circle is a type of ellipse but doesn’t share all its implementations, for example, stretching on one of the axes.

Thus, along with our previous criteria, we need to consider the Liskov Substitution Principle, which says, “the base class objects should be replaceable with the derived class objects and this replacement should not break the application.”

So, we should use inheritance if

  • there is an “is-a” relationship between classes (X is a Y)
  • the derived class can have all the functionality of the base class

For all other instances, the composition is the preferred choice.

Conclusion

In this article, we’ve learned about inheritance and composition. We’ve talked about both implementations, what are the differences, and is composition better than inheritance.