Intro

I love listening to podcasts on my way to work and during the last 3 months I heard about "The Gorilla/Banana problem" few times.

I think the lack of reusability comes in object-oriented languages, not functional languages. Because the problem with object-oriented languages is they’ve got all this implicit environment that they carry around with them. You wanted a banana but what you got was a gorilla holding the banana and the entire jungle.

-- Joe Armstrong

This problem can be solved with object composition (do not confuse with function composition). Both object composition and class inheritance have own benefits and drawbacks - none is perfect.

Before jumping to examples let's define what the inheritance and composition are:

Inheritance is when a class inherits methods and properties from another class.
Composition is when a class is composed of other classes.

Or:

Inheritance is when you design something around "what it is".
Composition is about designing something around "what it does".

The problem

Let's say we are creating a new game in "fighting" genre - a Mortal Kombat clone.
To play the game we have to design a player and enemy. In the first version both have two trivial abilities: .kick() and .finish().

Player
  .kick()
  .finish()

Enemy
  .finish()
  .kick()

Of course it makes sense to crate a shared class Character and move common methods into it.

Character
  .kick()
  .finish()

  Player
  Enemy

In the second version we decide to add a few different players and allow user to choose a player and an enemy among them.

Character
  .kick()
  .finish()

  LiuKang
  JohnnyCage
  SubZero
  Scorpion

So he can pick any player and any enemy. And both can just .kick() and .finish().
In the third version we decide to add more abilities. And to make the game more interesting, each player will have a unique set of them.

Character
  .finish()

  LiuKang
    .kick()
  JohnnyCage
    .knock()
  SubZero
    .kick()
    .knock()
  Scorpion
    .kick()
    .jump()

We move .kick() method out of Character class because it's not a common method for all players anymore and according to the new idea we want to limit player's abilities.
So we have got lots of code duplications here. Let's fix it:

Character
  .finish()

  Kicker
    .kick()

    LiuKang
    SubZero
      .knock()
    Scorpion
      .jump()

  JohnnyCage
    .knock()

Not bad, but we still have two identical implementations of .knock() method.
So what will happen if we add a few more abilities in the future? You are right, we will have a mess here.

Now take a look how this problem can be solved with object composition.

Solution

What if instead of defining what the player is able to do, we define 4 player types (one ability per one player type) and assign those types selectively to that player? Let's try.

PlayerType
  Finisher
    .finish()
  Kicker
    .kick()
  Knocker
    .knock()
  Jumper
    .jump()

Players    
  LiuKang
    PlayerType(Kicker, Finisher)
  JohnnyCage
    PlayerType(Knocker, Finisher)
  SubZero
    PlayerType(Kicker, Knocker, Finisher)
  Scorpion
    PlayerType(Kicker, Jumper, Finisher)

Implementation

Sorry for my ES2015, but it is really worth learning it.

Define player types

const knocker = state => ({
  knock: () => console.log(state.name + ': knock'),
});

const kicker = state => ({
  kick: () => console.log(state.name + ': kick'),
});

const jumper = state => ({
  jump: () => console.log(state.name + ': jump')
});

const finisher = state => ({
  finish: () => console.log(state.name + ': fatal finish')
});

Define available players

const PLAYERS = {
  LIU_KANG: 'Liu Kang',
  JOHNNY_CAGE: 'Johnny Cage',
  SUB_ZERO: 'Sub-Zero',
  SCORPION: 'Scorpion'
}

Define players factory

const createPlayer = (name) => {
  const state = { name, health: 100 };
  switch (name) {
    case PLAYERS.LIU_KANG:
      return Object.assign({}, kicker(state), finisher(state));

    case PLAYERS.JOHNNY_CAGE:
      return Object.assign({}, knocker(state), finisher(state));

    case PLAYERS.SUB_ZERO:
      return Object.assign({}, kicker(state), knocker(state), finisher(state));

    case PLAYERS.SCORPION:
      return Object.assign({}, kicker(state), jumper(state), finisher(state));
  }
};

Create players

const player = createPlayer(PLAYERS.SUB_ZERO);
const enemy = createPlayer(PLAYERS.SCORPION);

Both player object and enemy have 3 abilities:

Object.getOwnPropertyNames(player);
> [ 'kick', 'knock', 'finish' ]
Object.getOwnPropertyNames(enemy);
> [ 'kick', 'jump', 'finish' ]

Not so much, but it is a required minimum for the 3rd version of the game.
And nothing will stop us to create more players with more abilities in the future, because we own a factory which can produce them in an unlimited amount.

Fight simulation

player.kick();
enemy.jump();
player.knock();
enemy.kick();
player.knock();
player.finish();

Conclusion

Object composition is:

  • Flexible
  • Extensible
  • Easy to test
  • Better suited to agile development

This example is really primitive, but still it shows how the code can become unmaintainable within a few iterations if you use class inheritance.
However, using object composition all the time is not a silver bullet. You have to combine - use class inheritance for very predictable behaviors otherwise use object composition.