Unraveling the Magic of Composition over Inheritance

Robin Ingelbrecht
6 min readJun 17, 2023

--

Composition over inheritance” is a principle in programming that emphasizes the use of object composition instead of class inheritance to achieve code maintainability. While inheritance can be useful in certain scenarios, it can also lead to tight coupling and inflexible designs.

By favoring composition, developers can build systems that are more adaptable, extensible, and maintainable. It encourages the creation of loosely coupled components, testability, and separation of concerns.

Photo by KOBU Agency on Unsplash

I know the intro is very abstract and theoretical, but don’t worry, I got you covered. I recently created a library to generate Rubik’s Cube scrambles and quickly ran into issues while using class inheritance.

For those of you who do not know, a Rubik’s Cube is one of many types of “Twisty puzzles”. Each twisty puzzle has its own scramble notation and scramble rules. A lot of these notations and rules are very similar, that’s why class inheritance seemed like a great idea at first.

The class inheritance solution

I started off by creating a base class Scramble

abstract class Scramble
{
private array $turns;

protected function __construct(
Turn ...$turns,
) {
$this->turns = $turns;
}

public function getTurns(): array
{
return $this->turns;
}

public function reverse(): Scramble
{
// Code to reverse scramble.
return $this;
}

public function forHumans(): string
{
// Code to convert scramble to human-readable format.
return $forHumans;
}

abstract public static function random(int $scrambleSize): self;

abstract public static function fromNotation(string $notation): self;
}

This class contains the basic functionality of a scramble as well as two abstract methods that have to be implemented by any class that extends it.

Next, I created the first actual implementation of a scramble and I already had to override the parent constructor because this scramble needed to be aware of its own size.

final class CubeScramble extends Scramble
{
private function __construct(
private readonly Size $size,
Turn ...$turns,
) {
parent::__construct(...$turns);
}

public static function random(int $scrambleSize, Size $size = null): Scramble
{
// Generate a randomized scramble.

return new self($size, ...$turns);
}

public static function fromNotation(string $notation, Size $size = null): Scramble
{
// Create scramble from given notation.

return new self($size, ...$turns);
}
}

// Usage.
$scramble = CubeScramble::random(10);

So far, so good. The next scramble I needed to implement, had a slightly different behaviour in that it was not possible to reverse it:

final class Sq1Scramble extends Scramble
{
public static function random(int $scrambleSize, Size $size = null): Scramble
{
// Generate a randomized scramble.

return new self(...$turns);
}

public static function fromNotation(string $notation, Size $size = null): Scramble
{
// Create scramble from given notation.

return new self(...$turns);
}

public function reverse(): Scramble
{
throw new \RuntimeException('Not implemented');
}
}

The third implementation had another slightly different behaviour. Impossible to reverse and the human-readable notation was different as well.

final class MegaminxScramble extends Scramble
{
public static function random(int $scrambleSize, int $numberOfSequences = 7): Scramble
{
// Generate a randomized scramble.

return new self(...$turns);
}

public static function fromNotation(string $notation): Scramble
{
// Create scramble from given notation.

return new self(...$turns);
}

public function reverse(): Scramble
{
throw new \RuntimeException('Not implemented');
}

public function forHumans(): string
{
// Code that overrides default behaviour for human-readable notation.
return $forHumans;
}

}

At this point, I started questioning my decision to use a base class. I felt my code was too tightly coupled and I had to do a lot of nasty things to make everything work.

The “in-between” solution

After some tinkering, I decided to implement a decorator pattern. This approach would allow me to delegate work to decorated objects instead of using a base class to do this.

I changed the abstract Scramble class to an interface

interface Scramble
{
public function getTurns(): array;

public function reverse(): self;

public function forHumans(): string;

public static function random(): Scramble;

public static function fromNotation(string $notation): Scramble;
}

Next, I created an implementation for this interface, SimpleScramble. This basically was the old abstract Scramble class

final class SimpleScramble implements Scramble
{
private array $turns;

public function __construct(
Turn ...$turns,
) {
$this->turns = $turns;
}

public function getTurns(): array
{
return $this->turns;
}

public function reverse(): Scramble
{
// Code to reverse scramble.

return $this;
}

public function forHumans(): string
{
// Code to convert scramble to human-readable format.
return $forHumans;
}
}

Notice this class is now marked as final and therefore cannot be extended anymore. It’s still there to function as some kind of base class though. Let’s see how the CubeScramble class, which used to extend Scramble now looks like

class CubeScramble implements Scramble
{
private function __construct(
private readonly Size $size,
private readonly Scramble $scramble,
) {
}

public static function random(int $scrambleSize, Size $size = null): Scramble
{
// This class now initalizes (decorates) a SimpleScramble object.
// This allows us to delegate the work to the SimpleScramble.
// Notice that both SimpleScrabmle and CubeScramble implement Scramble.
return new self($size, new SimpleScramble(...$turns));
}

public static function fromNotation(string $notation, Size $size = null): Scramble
{
// This class now initalizes (decorates) a SimpleScramble object.
// This allows us to delegate the work to the SimpleScramble.
// Notice that both SimpleScrabmle and CubeScramble implement Scramble.
return new self($size, new SimpleScramble(...$turns));
}

public function getSize(): Size
{
return $this->size;
}

public function getTurns(): array
{
return $this->scramble->getTurns();
}

public function reverse(): Scramble
{
return new self($this->getSize(), $this->scramble->reverse());
}

public function forHumans(): string
{
return $this->scramble->forHumans();
}
}

The usage of this class stayed exactly the same:

$scrabmle = CubeScramble::random(10);

But the inner workings changed quite a bit:

  • We now need to implement all the methods defined on the Scramble interface
  • Instead of a parent class, which now is non-existent, doing the heavy lifting for us, we now delegate this to the decorated object. In this case SimpleScramble object.

I was quite happy with this refactor, but I still needed to throw exceptions for methods that could not be implemented for certain scrambles:

public function reverse(): Scramble
{
throw new \RuntimeException('Not implemented');
}

My code was still too tightly coupled, back to the drawing board.

The composition solution

I remembered scrolling Reddit and reading this comment:

An interface having one method is great. An interface having 2–3 methods is ok. An interface having 4 or more methods probably needs your attention.

With this in mind, I took a close look at the Scramble interface. It was clear that it “needed attention”. Composition is all about loosely coupled components, so that’s what I set out to do: split up my interface into separate behaviours.

The Scramble interface got reduced to

interface Scramble
{
public function getTurns(): array;
}

Next, I introduced four new interfaces

interface Reversible
{
public function reverse(): Scramble;
}
interface Randomizable
{
public static function random(): Scramble;
}
interface HumanReadable
{
public function forHumans(): string;
}
interface FromNotation
{
public static function fromNotation(string $notation): Scramble;
}

With these interfaces in place, I was able to define separate behaviours for each of the different scrambles without resorting to throwing exceptions or doing other nasty stuff.

final class CubeScramble implements Scramble, Reversible, HumanReadable, Randomizable, FromNotation
{
// ...
}

Or if a scramble is not reversible:

final class Sq1Scramble implements Scramble, HumanReadable, Randomizable, FromNotation
{
// ...
}

Or if it is not reversible and cannot be parsed to a human-readable notation:

final class MegaminxScramble implements Scramble, Randomizable, FromNotation
{
// ...
}

I’m really satisfied with this approach and I think this separation of behaviours will definitely benefit me when I need to add new types of scrambles in the future.

Be cautious though

Richard Kenneth has a very good point that inheritance should not be avoided.

Both composition and inheritance are tools, and tools are not inherently good or bad. They should be used where appropriate.

Religiously and universally choosing composition over inheritance is a bad idea. The fact is, inheritance makes sense in many situations. It can encourage code reuse. It can be economical. It can improve understanding of the software architecture.

Sure, inheritance has cons, too. Software engineering is always a balancing act, a decision matrix that leads to engineering trade-offs.

Example code

--

--

Robin Ingelbrecht
Robin Ingelbrecht

Written by Robin Ingelbrecht

My name is Robin Ingelbrecht, and I'm an open source (web) developer at heart and always try to up my game. Obviously, I'm also into gaming 🎮.

No responses yet