Composition over Inheritance

As with any decision between two choices, there will be benefits and disadvantages to each. Sometimes its clearly obvious which choice should be the winning choice. In this issue we’ll talk about object composition over inheritance and the benefits and disadvantages to each.

What are the typical forms of extending a classes behavior?

Strictly speaking most object oriented programming languages provide two mechanisms to allow extending a object or classes behavior.

First is Object inheritance. The idea that an objects child can be treated as if it were the parent is the very definition of polymorphism. If AdminUser is an extension of or inherits from User, any place you should be able to pass in user, you should be able to pass in an AdminUser. To access additional properties present on only the admin user, you will have to cast to appropriate types. Additionally using the wrong access modifiers on properties can lead to unexpected results. In c# if a method is not defined as virtual, and you override or use the new keyword, you provide a new definition of the method… but only when treated as the child type. As soon as you attempt to take advantage of polymorphism, with a new method… you get two potentially different sets of behavior. Object inheritance can provide default implementations as well as force the overriding of abstract methods. Some languages even support multiple inheritance… (they have two or more parent classes and inherent methods and properties from each) but I’m not so sure that is necessarily a good thing.

public class User {
public string FirstName {get;set;}
public string LastName {get;set;}

public void PrintName()
{
Console.WriteLine($"Hello from {FirstName} {LastName});
}
}
public class AdminUser: User {
public bool IsAdmin {get{ return true;} set;}
public new void PrintName()
{
Console.WriteLine($"Whoooooops");
}
}
var user = new AdminUser{ FirstName = "Bob", LastName = "Jones"};
user.PrintName();
((User)user).PrintName();

Unlike object inheritance, interface implementation provides no default implementation (c# has interesting proposals to actually change that), and is really just a guarantee that that this implementation of this class guarantees it supports a contract of sorts.

public interface ICanBark
{
void Bark();
}
public interface ICanWalk
{
void WalkTo(string location);
}
public class Dog: ICanBark, ICanWalk
{
public void Bark()
{
Console.WriteLine("Woof Woof");
}
   public void WalkTo(string location)
{
Console.WriteLine($"Walked to {location}");
}
}

Composition over Inheritance

So now we have had a refresher over the differences around interface implementation and object inheritance, lets discuss why in a lot of scenarios, people tend to suggest composition over inheritance.

Lets say we want two or more sets of traits implemented on a single object. Programming languages that support singular inheritance would require very careful construction, and probably too many more levels of inheritance. Sometimes, those object inheritance patterns just wouldn’t make sense. In the example above for interface implementation… properties about whether or not this object can walk, or can bark may not make sense at all to contain in the same object hierarchy.

Instead, consider using one or more interfaces with the requirements for you implementation and create a composited object. With the example of the dog class we can build services specific to things we care about on the dog class, and ignore things we dont care about.

public class AnimalWalkingService
{
public void WalkAnimal(ICanWalk walkableAnimal, string location)
{
walkableAnimal.WalkTo(location);
}
}

This should in theory lead to more direct designs that don’t require tip toeing around language limitations… while providing guarantees about required logic… logic that can be totally totally disparate and unrelated.

In closing

So there you have it! Object oriented programming langues give us a lot of power to solve problems in different ways. They provide different mechanisms to continue the concepts around code reuse and keeping things simple, and provide us with contexts that help our feeble minds draw parallels with real world objects.

Understanding the difference between the two and where each might be applicable probably wont make or break your project, but could definitely help you to understand how better to build code destined for successful evolution.

Author: Pawan Kumar

Leave a Reply

Close Menu
%d bloggers like this:
Skip to toolbar