Inheritance vs Composition: Composition wins by knockout
Both people who've read my book and anyone who I've taught at university will know that I cover Object-Oriented Programming in detail but never mention inheritance once and never give examples of it. There's a reason for that.
As part of my PhD research I've been collecting programmer's attitudes towards inheritance (among other programming practices) and I've yet to come across an example where it's the better option.
Often developers will say things like:
we recommend preferring composition vs inheritance, and only using inheritance when the using composition would be impractical.
Or the oft-repeated "favour composition over inheritance". This is a cop out. I've yet to find a single case where inheritance offers any distinct advantage over composition. If you need polymorphic behaviour, use interfaces and for relationships composition is always easier to implement and far more flexible.
Here's a phrase I'd prefer to see over "favour composition over inheritance": Any is-a relationship can be expressed using has-a. As it gets the point across more strongly.
James Gosling, the creator of Java once said:
You should avoid implementation inheritance whenever possible.
And I've yet to be shown a case where it's not possible, at least when defining relationships between your own classes. If it's always possible to avoid inheritance we can infer that You should avoid implementation inheritance is the point he is making here, and I agree.
In this article, I'll show you how you can model any is-a inheritance relationship using has-a composition.
Is avoiding inheritance impossible?
When is avoiding inheritance impossible? I can think of a single case: When you do not own the base type but need to override its behaviour while retaining polymorphism. For example, logging all queries sent to the PDO class in PHP:
class LoggingPDO extends PDO {
public function query($sql) {
$this->logger->log($sql);
return parent::query($sql);
}
}
Even then, the adapter pattern using composition may be preferable. Other than that, if you are dealing entirely with classes you own, there is no case when using composition is impossible and I'll show you why.
I'm not going to differentiate between composition and aggregation here, simply because it doesn't matter for these examples.
Employee extends Person
Let's take a few classic examples. Firstly Employee extends Person:
class Person {
private $name;
private $dateOfBirth;
}
class Employee extends Person {
private $employeeNumber;
private $jobTitle;
private $salary;
}
I've left out the methods and just concentrated on the properties for this example.
This is a classic is-a relationship starting with a generic Person
class and increasing specificity in the Employee
class. An employee is-a person.
Person has-a job
There is no reason this needs to be modeled using inheritance. Instead of thinking of an employee as a person you can model it as a person who has a job
class Employee {
private $worker;
private $job;
public function __construct($worker, $job) {
$this->worker = $worker;
$this->job = $job;
}
}
$employee = new Employee(new Person, new Job);
I've modeled the same relationship but in a much more flexible manner. Using inheritance, only people can ever be employees. By using composition, employees can be other types:
$guideDog = new Employee(new Dog, new Job);
$marsRover = new Employee(new Robot, new Job);
Using inheritance because Employee extends Person
there is no way to have an employee who is a robot or a dog. Perhaps not an issue when the system is built, but may cause trouble in the future.
Any is-a relationship can be flipped around like this and converted into a has-a relationship. A Tesla
is-a Car
and a Ferrari
is-a Car
can be flipped around based on the difference that the two variants have. A Tesla
has-a ElectricEngine
and a Ferrari
has-a PetrolEngine
Let's try a more extensive example with some methods.
Cat extends Animal
Here's another classic inheritance example, and using this example I'm going to show why inheritance sucks at even the most simple and contrived tasks:
abstract class Animal {
private $fed = false;
public function speak(): string;
public function eat(Food $food): bool;
public function getNumLegs(): int;
}
class Cat extends Animal {
public function speak(): string {
return 'meow';
}
// Feed the cat, if the cat cannot eat the type of food it's given, $fed should remain false
public function eat(Food $food): bool {
if ($food->isMeat()) {
$this->fed = true;
return true;
}
else {
return false;
}
}
public function getNumLegs(): int {
return 4;
}
}
class Dog extends Animal {
public function speak(): string {
return 'woof';
}
// Feed the dog, dogs can eat anything but chocolate
public function eat(Food $food): bool {
if (Food instanceof Chocolate) {
return false;
}
else {
$this->fed = true;
return true;
}
}
public function getNumLegs(): int {
return 4;
}
}
class Cow extends Animal {
public function speak(): string {
return 'moo';
}
// Feed the cow, cows cannot eat meat
public function eat(Food $food): bool {
if ($food->isMeat()) {
return false;
}
else {
$this->fed = true;
return true;
}
}
public function getNumLegs(): int {
return 4;
}
}
This looks sensible and follows a textbook intro to OOP example. We have a generic animal class and provide behaviour for specific animals by extending the class.
But why is inheritance used here? There is no reason this needs to be modeled using inheritance. Ignoring the eat
method for a moment, the number of legs and speak string are simple properties. The cow, dog and cat could all be modeled using a single class without inheritance or composition:
class Animal {
private $speak;
private $numLegs;
public function __construct(string $speak, int $numLegs) {
$this->speak = $speak;
$this->numLegs = $numLegs;
}
public function speak(): string {
return $this->speak;
}
public function getNumLegs(): int {
return $this->numLegs;
}
}
$cat = new Animal('meow', 4);
$cow = new Animal('moo', 4);
$dog = new Animal('woof', 4);
This is less code as only one class needs to be defined and can be more easily extended with new animals. I can create new animals by creating an instance, without needing to write a whole new class.
So what about the eat
method that contains some actual behaviour, rather than just a property?
That's slightly harder to model, but it is still easier with composition than inheritance:
interface Diet {
public function canEat(Food $food): bool;
}
class CarnivoreDiet implements Diet {
public function canEat(Food $food): bool {
return $food->isMeat();
}
}
class HerbivoreDiet implements Diet {
public function canEat(Food $food): bool {
return !$food->isMeat();
}
}
class OmnivoreDiet implements Diet {
public function canEat(Food $food): bool {
return true;
}
}
The type of diet is separated out from determining whether the animal is fed. The Animal class can then require a Diet instance via composition to allow modeling of animals with different diets:
class Animal {
private $speak;
private $numLegs;
private $diet;
private $fed = false;
public function __construct(string $speak, int $numLegs, Diet $diet) {
$this->speak = $speak;
$this->numLegs = $numLegs;
$this->diet = $diet;
}
public function speak(): string {
return $this->speak;
}
public function getNumLegs(): int {
return $this->numLegs;
}
public function eat(Food $food): bool {
if ($this->diet->canEat($food)) {
$this->fed = true;
return true;
}
else {
return false;
}
}
}
I've created the exact same class behaviour without needing any subclasses of Animal
, yet the different animal types can still be modeled in the application:
$cat = new Animal('meow', 4, new CarnivoreDiet());
$cow = new Animal('moo', 4, new HerbivoreDiet()));
When unique behaviour is required, for example, a dog being able to eat anything but chocolate, the system can very easily be extended:
class DogDiet implements Diet {
public function canEat(Food $food): bool {
if ($food instanceof Chocolate) {
return false;
}
else {
return true;
}
}
}
$dog = new Animal('woof', 4, new DogDiet());
Not only is inheritance not required here, but using composition requires less code and is considerably more flexible.
Inheritance sucks
The problems with inheritance very quickly become apparent. Imagine I wanted to extend the system to model a pig and a human:
class Pig extends Animal {
public function speak(): string {
return 'oink';
}
public function eat(Food $food): bool {
$this->fed = true;
return true;
}
public function getNumLegs(): int {
return 4;
}
}
class Human extends Animal {
public function speak(): string {
return 'Hello';
}
public function eat(Food $food): bool {
$this->fed = true;
return true;
}
public function getNumLegs(): int {
return 2;
}
}
The obvious problem here is that the eat
method for both Pig
and Human
classes are identical. The fix as we're using inheritance is an extra level of inheritance:
class Omnivore extends Animal {
public function eat(Food $food): bool {
$this->fed = true;
return true;
}
}
class Pig extends Omnivore {
public function speak(): string {
return 'oink';
}
public function getNumLegs(): int {
return 4;
}
}
class Human extends Omnivore {
public function speak(): string {
return 'Hello';
}
public function getNumLegs(): int {
return 2;
}
}
This works, but not all humans are omnivores, some do not eat meat. How can we model vegetarian humans in this system? The solution using inheritance is: More inheritance!
class VegetarianHuman extends Human {
//Override parent method
public function eat(Food $food): bool {
if ($food->isMeat()) {
return false;
}
else {
$this->fed = true;
return true;
}
}
}
But now we've introduced even more duplicated code: the eat
method for VegetarianHuman
and Cow
are the same and there's no way to fix it without duplicated code somewhere.
Using inheritance, there is no way to model, VegetarianHuman
and Human
without repeating code somewhere.
Either VegetarianHuman
needs extend Human
and override the eat
method as above or VegetarianHuman
needs to extend Herbivore
and duplicate the methods in the Human
class:
class VegetarianHuman extends Herbivore {
// These two methods are identical to the methods in the Human class
public function speak(): string {
return 'Hello';
}
public function getNumLegs(): int {
return 2;
}
}
Inheritance always ends up with these kinds of difficulties because it creates a rigid tightly coupled structures and you have to be incredibly careful how you design your classes in order to avoid repeated code. And as I've just shown, it's not always possible even in fairly trivial examples.
Consider the same without using inheritance:
$pig = new Animal('oink', 4, new OmnivoreDiet());
$cow = new Animal('moo', 4, new HerbivoreDiet());
$human = new Animal('hello', 2, new OmnivoreDiet());
$vegetarianHuman = new Animal('hello', 2, new HerbivoreDiet());
By avoiding inheritance I can give any kind of animal any kind of diet because it's not reliant on rigid hierarchies. To create an animal with a special kind of diet, I can create a new type of Diet
and inject it into an Animal
instance.
You don't need inheritance. Ever. Really.
If you are coming from a background of using inheritance it can be difficult to change your frame of mind from is-a to has-a but once you start getting the hang of thinking in terms of objects with properties rather than types with subtypes it becomes incredibly easy.
To change your way of thinking, assume you create an instance of the base class and assign behaviors to it. Rather than a Manager
that is-a Employee
consider a Manager
a Person
that has-a ManagerialJob
.
Along with the practical issue of repeated code I showed here, inheritance breaks encapsulation and introduces tight coupling.
There's no reason to use inheritance over composition when defining relationships. Ever. every is-a relationship can be expressed as a has-a relationship. I haven't used, or needed to consider using, inheritance in any of my projects in over 5 years. Take a look at Transphporm, Maphper or XMarkDown none of these projects use inheritance anywhere. It's simply not required and does more harm than good and not something I ever even think about until I encounter it in someone else's code.
As I said at the start, unless you need to override a method in a base class that you didn't write, and the adapter pattern cannot be used, you don't need inheritance, ever. In this case you're not defining a relationship, but overriding some behaviour in a class someone else wrote. And you only need to do that because the original class wasn't designed with this flexibility in mind.