Cover
PHP

Composition or Inheritance, when to use which?

Introduction
Composition or Inheritance, when to use which?

As a follow up of my previous blog post, I wanted to dive a bit more into the aspect of improving skills as a developer. To do this I would like to take you on a journey to learn about the differences between composition and inheritance (and when to chose which).

The composition vs inheritance topic is something that is widely talked about on the internet. In general there seems to be consensus on when to use which, but as with the majority of the patterns / rules for software engineers, there is no one size fits all solution. Personally I defined my own 'guidelines' for when to use which by trail and error, and I'd like to take you with me on a journey to dive into these guidelines, this so that you can apply it in your life as well to further enhance your skills as a developer!

What is composition?

The goal of composition is to have classes be 'composed' out of smaller components, an example of this can be found below:

<?php

class UserValidator
{
    public function __construct(EmailValidator $email_validator)
    {
        // ... code
    }

    public function isValid(User $user): bool
    {
        return $this->email_validator->isValid($user->email);
    }
}

In the above example, we've composed the UserValidator class with the help of the sub class EmailValidator. As you will see further down in this blog post, this is one way of building classes, it would also have been possible to use inheritance to build this UserValidator.

Now that we've seen composition, it's time to look at inheritance!

What is inheritance?

A different way to structure classes is by using inheritance, an example of this can be found below:

<?php

class UserValidator extends Validator
{
    public function isValid(User $user): bool
    {
        return $this->email_validator->isValid($user->email);
    }
}

In the above example, the email_validator has been defined in the parent Validator, thus instead of passing an instance of the EmailValidator, it will be created through the hierarchy chain when instantiating this class.

Advantages and disadvantages

Now that we have an idea what composition and inheritance entail, it's time to look at the advantages and disadvantages of both:

Advantages of composition:

It promotes lose coupling.

  • This because you can pass different variations to the constructor which also helps with testing (since you can mock and stub the variation).

It promotes code re-use.

  • You can have a single object (e.g. the EmailValidator in this case) also be reused by other components if they need them.

It exposes over-complicated classes.

  • By using composition you can quickly notice when a class does too much. This because the moment you have to pass 8 or more classes to instantiate a new class, it might be time to see if it needs to be refactored.

Disadvantages of composition:

It does not allow passing subclasses to a method.

  • You are for example unable to pass a composed Cat to a function that takes an Animal. This because this feature belongs to inheritance. In these cases it might be desired to combine both composition and inheritance (for example, by using interfaces).

Advantages of inheritance:

Enables passing objects by interface (e.g. you can pass a Cat to a function that expects an Animal).

  • This can be very helpful to increase the abstractness of your code, since instead of making modifications to the currently existing code, you can create a new subclass of an interface / abstract class. This also adheres to the Open/Closed principle.

Disadvantages of inheritance:

Due to the 'tight coupling' its difficult to write unit tests.

  • In order to achieve a high percentage test coverage, you would have to test all the variations of the implementations of the functions.

It is difficult to change the class hierarchy once deployed, thus it breaks encapsulation.

  • The moment you have a hierarchy chain, e.g. Cat extends Animal, it's quite a lot of work to add a new 'member' between these two (e.g. Mammal). To do this, you carefully need to evaluate both the Cat and Animal class in order to 'learn' what you can place in the new Mammal class.

As you might have noticed above, in general, the strenghts of composition are the weaknesses of inheritance and vice versa. This because they are not mutually exclusive, to write a good software system, you likely have to combine both composition and inheritance. In order to help you decide when to use which method, lets look at the guidelines that I've set for myself:

When would I use which?

Whilst the example in this post has been biased towards a composition-favored approach, it does not mean that there are no valid cases for inheritance. As you have seen, both methods have advantages and disadvantages.

The guideline that I've set for myself (and often seems to be referred to / used in books / literature available online), is to think about 'what' kind of relationship I'm creating:

If there is a has-a(n) relationship, I would generally use composition.

  • E.g. a Car has-an Engine, an Animal has-a DigestiveSystem.

If there is an is-a(n) relationship, I would generally use inheritance.

  • E.g. a Car is-a Vehicle, a Cat is-an Animal.

As mentioned earlier, the beauty of our craft, is that it is sometimes more of an art then a science. So whilst the above can be used as a guideline, it's not a silver lining, there can always be exceptions to these guidelines in which case the 'other' method might be better (or in some cases, that you're combining composition and inheritance together).

Do you agree with my intepretations? Do you use a different method to distinctiate between composition v.s. inheritance? Please let me know in the comments down below!

Vasco de Krijger
View Comments
Next Post

Software Philosophy: Data at the outer layer

Previous Post

7 lessons I've learned during my career as a developer.

Success! Your membership now is active.