DEV Community

Cover image for Learn and use Composition in JavaScript and TypeScript
Chris Noring for ITNEXT

Posted on

Learn and use Composition in JavaScript and TypeScript

Follow me on Twitter, happy to take your suggestions on topics or improvements /Chris

What does the word composition mean? Let's start with the verb to compose. Looking up a dictionary you find a lot of references to composing music :) You will also find this definition:

to be formed from various things

The above is probably closer to what I'm about to talk about next - Composition.

Composition

The idea is to create something from other parts. Why would we want to do that? Well, it's easier to build something complex if it consists of many small parts that we understand. The other big reason is reusability. Bigger and more complex things can sometimes share parts they consist of, with other big and complex things. Ever heard of IKEA? ;)

There's more than one type of composition in programming, who knew? ;)

Composition vs Inheritance

Let's quickly explain inheritance. The idea with inheritance is to inherit traits, fields as well as methods from a parent class. The class inheriting from a parent class is called a subclass. This inheritance makes it possible to treat a group of objects in the same way. Consider the below example:

class Shape {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
}

class Movable extends Shape {
  move(dx, dy) {
    this.x += dx;
    this.y += dy;
  }
}

class Hero extends Movable {
  constructor(x, y) {
    super(x, y);
    this.name = 'hero';
  }
}

class Monster extends Movable {
  constructor(x, y) {
    super(x, y);
    this.name = 'monster';
  }
}

const movables = [new Hero(1,1), new Monster(2,2)];
movables.forEach(m => m.move(2, 3));

Above we can treat instances of Hero and Monster in the same way as they have a common ancestor Movable that allows them to be moved through the move() method. All this is based on a relationship principle IS-A. A Hero IS-A Movable, a Monster is a Movable.

There's been a lot of talks lately on how you should favor composition over inheritance, why is that? Let's look at some drawbacks with inheritance:

  • Liskov substitution principle is done wrong, the idea behind this principle is the code should still work if I replace something with a common ancestor for something else, i.e replacing a Hero for a Monster, they are both of type Movable. In the above sample code that replacement should work. However, the above code represents the ideal case. Reality is much worse. In reality, there might exist large codebases where there are 20+ levels of inheritance, and inherited methods might not be implemented properly meaning that certain objects can't be replaced for one another. Consider the following code:
   class NPC extends Movable {
     move(dx, dy) {
       console.log('I wont move')
     }
   }

The above have broken the substitution principle. Now, our codebase is so small so we can spot it. In larger codebases, you might not be able to spot when this happens. Imagine this happens on inheritance level 3 and you have 20 levels of inheritance.

  • Lack of Flexibility, a lot of the time you have a HAS-A relationship over IS-A. It's easier to think of different components doing various things rather than them having a commonality, a common ancestor. This may lead us to create a lot of extra classes and inheritance chains when a composition would have been more appropriate.

Function composition

It's a mathematical term stating that states the following according to Wikipedia, function composition is an operation that takes two functions f and g and produces a function h such that h(x) = g(f(x)). Related to programming is when we apply at least two functions on something like so:

   let list = [1,2,3];
   take(orderByAscending(list), 3)

Above imagine that list is x, f(x) is orderByAscending(list) and g(x) is take() with f(x) as input parameter.

The point is you have many operations applied one after another on a piece of data. Essentially you apply different parts to create a more complex algorithm that when invoked computes a more complex result. We won't spend so much time talking about this version of composition but know that it exists.

Object Composition

This type of composition is about combining objects or other data types to create something more complex than what we started with. This can be accomplished in different ways depending on what language you have in front of you. In Java and C# you only have one way to create objects, by using a class. In other languages like JavaScript, you have objects that can be created in many ways and thereby you open up for different ways of composing.

Object composition with Classes

Using classes is about a class referencing one or more other classes via instance variables. It describes a has-a association. Which means what exactly? A person has four limbs, a car may have 4 wheels, and so on. Think of these classes being referenced as parts or composites that gives you the ability to do something, a capability. Let's look at an example:

   class SteeringWheel {
     steer(){}
   }

   class Engine {
     run(){}
   }

   class Car {
     constructor(steeringWheel, engine) {
       this.steeringWheel = steeringWheel;
       this.engine = engine;
      }

     steer() {
       this.steeringWheel.steer();
     }

     run() {
       this.engine.run();
     }
   }

   class Tractor {
     constructor(steeringWheel) {
       this.steeringWheel = steeringWheel;
     }

     steer() {
       this.steeringWheel.steer();
     }
   }

What we are getting above is first a more complex class Car consisting of many parts steeringWheel and engine and through that, we gain the ability to steer and a vehicle that runs. We also get reusability as we can use the SteeringWheel and use it in a Tractor.

Object composition without classes

JavaScript is a little different than C# and Java in that it can create objects in a number of ways like the following:

  • Object literal, you can create an object by just typing it out like so:
   let myObject { name: 'a name' }`
  • Object.create(), you can just pass in an object and it will use that as template. Like this for example:
   const template = {
     a: 1,
     print() { return this.a }
   }

   const test = Object.create(template);
   test.a = 2
   console.log(test.print()); // 2
  • Using new. You can apply the new operator on both a class and a function, like so:
   class Plane {
     constructor() {
       this.name = 'a plane'
     }
   }

   function AlsoAPlane() {
     this.name = 'a plane';
   }

   const plane = new Plane();
   console.log(plane.name); // a plane

   const anotherPlane = new AlsoAPlane();
   console.log(anotherPlane) // a plane

There is a difference between the two approaches. You need to do a bit more work if you want inheritance to work for the functional approach among other things. For now, we are happy knowing that there are different ways to create objects using new.

So how would we actually compose? To compose we need a way to express behavior. We don't need to use classes if we don't want to, but we can skip directly to objects instead. We can express our composites in the following way:

const steer = {
  steer() {
    console.log(`steering ${this.name}`)
  }
}

const run = {
  run() {
    console.log(`${this.name} is running`)
  }
}

const fly = {
  fly() {
    console.log(`${this.name} is flying`)
  }
}

and compose them like so:

const steerAndRun = { ...steer, ...run };
const flyingAndRunning = { ...run, ...fly };

Above we are using the spread operator to combine different properties from different classes and place them into one class. The resulting steerAndRun now contains { steer(){ ... }, run() { ... } } and flyingAndRunning contains { fly(){...}, run() {...} }.

Then using the method createVehicle() we create what we need like so:

function createVehicle(name, behaviors) {
  return {
    name,
    ...behaviors
  }
}

const car = createVehicle('Car', steerAndRun)
car.steer();
car.run();

const crappyDelorean = createVehicle('Crappy Delorean', flyingAndRunning)
crappyDelorean.run();
crappyDelorean.fly();

The end result is two different objects with different capabilities.

But I use TypeScript, what then

TypeScript makes heavy uses of classes and interfaces and that's a way to accomplish object composition using classes.

Can I accomplish composition in a way similar to using the spread operator and objects?

Yes, yes you can. Hold on. We're going to use a concept called MixIns. Let's begin:

  1. First, we need this construct:
   type Constructor<T = {}> = new (...args: any[]) => T

We use this construct to express that Constructor is something that can be instantiated.

  1. Next, declare this function:
   function Warrior<TBase extends Constructor>(Base: TBase) {
     return class extends Base {
       say: string = 'Attaaack';
       attack() { console.log("attacking...") }
     }
   }

What's returned is a class inheriting from Base. Base is the input parameter to our function and is of type TBase, that uses the Constructor type we just created.

  1. Let's define a class that will use the above function:
   class Player {
     constructor( private name: string ) {}
   }
  1. Now, invoke the Warrior function like so:
   const WarriorPlayerType = Warrior(Player);
   const warrior = new WarriorPlayerType("Conan");
   console.log(warrior.say); // 'Attaaack'
   warrior.attack(); // 'attacking...'
  1. We can keep composing by creating a new function that holds another behavior we might want:
   function Wings<TBase extends Constructor>(Base: TBase) {
     return class extends Base {
       fly() { console.log("flying...") }
     }
   }
  1. Let's use that on an existing composition
   const WingsWarriorPlayerType = Wings(WarriorPlayerType);
   const flyingWarrior = new WingsWarriorPlayerType("Flying Conan");
   flyingWarrior.fly();

Summary

This article described what composition is. Additionally, it talked about how composition is favored over inheritance. Then it covered that there are different ways to achieve composition. As a bonus, we also covered an approach you can use in TypeScript.

Further reading

There are some great articles on this topic that I think you should read. Have a look at the following:

Top comments (10)

Collapse
 
greggman profile image
Greggman

This looks like it has issues compared to the composition I'm used to.

These are not real examples. A real composition each piece being composited will have local state and that local state will be per instance. In other words, I need a steer the has a this.direction and I need a run that also has a this.direction and those should not be the same direction because there can be 1000s of things to composite and there would be no easy way for programmers to know they're all using direction in the same ways.

Collapse
 
koresar profile image
Vasyl Boroviak

Well, you've just dissed Martin Fowler's definition of "code smell".

"code smell" != "certainly bad code"

Collapse
 
greggman profile image
Greggman

By any definition of code smell, having to look at the internals of every function you composite to make sure it's not going to conflict with any other function is the very definition of stinky code. Gees

Collapse
 
koresar profile image
Vasyl Boroviak

IMHO, 1000s of things to compose is a code smell.

Collapse
 
greggman profile image
Greggman

Well you just dissed every Unity game that's shipped and all Unity devs so I guess it's you against them. Every Unity game is 1000s of things to compose.

Thread Thread
 
softchris profile image
Chris Noring

I wonder if this is a typical thing in games particularly. Have you seen this kind of composition elsewhere? I'd appreciate any code samples that show the kind of composition you describe.

Collapse
 
jeffmottor profile image
Info Comment hidden by post author - thread only accessible via permalink
Jeff-Mott-OR

I'm sorry to say that you've been bamboozled. There's unfortunately misinformation circulating within the JavaScript community about object composition. That pattern you're showing here isn't object composition at all. In fact it's quite literally the opposite of composition. It's inheritance. It's multiple inheritance, to be more specific.

I'm going to show some examples in Python, because Python natively supports multiple inheritance. First up, just some basic single inheritance 101.

class Barker:
    def bark(self):
        print('Woof Woof!')

class Dog(Barker):
    pass # "pass" is the Python equivalent of an empty block {}

jack = Dog()
jack.bark() # Woof Woof!

The class Dog(Barker): part is Python syntax for inheritance. It may not spell out the word "extends", but you can recognize inheritance when you see it, right? In this example, "Dog" is the child class, and it inherits the properties and behavior of the parent, "Barker", which is why we can call "bark" on the child object.

But dogs need to eat too. Here's a slightly different example but still basic single inheritance 101.

class Eater:
    def eat(self):
        print(f'{self.name} is eating.')

class Dog(Eater):
    def __init__(self, name):
        self.name = name

jack = Dog('Jack')
jack.eat() # Jack is eating

In this example, "Dog" is the child class, and it inherits the properties and behavior of the parent, "Eater", which is why we can call "eat" on the child object. The only other difference here is the parent class, "Eater", uses an instance field, "name", that it expects the child class to provide. But otherwise this is still inheritance 101.

But dogs *both* bark and eat, don't they? We don't want to inherit just bark or inherit just eat. We want to inherit both. The feature to do that is called multiple inheritance, and in Python it's as easy as a comma.

class Barker:
    def bark(self):
        print('Woof Woof!')

class Eater:
    def eat(self):
        print(f'{self.name} is eating.')

class Dog(Barker, Eater):
    def __init__(self, name):
        self.name = name

jack = Dog('Jack')
jack.bark() # Woof Woof!
jack.eat() # Jack is eating

In this example, "Dog" is still the child class, and it inherits the properties and behavior of two parents, "Barker" and "Eater", which is why we can call both "bark" and "eat" on the child object.

But... this is starting to look eerily familiar, right? This is the kind of thing you've been doing with the spread operator, the kind of thing you've been misled into believing is object composition. It may not spell out the word "extends", but you can recognize inheritance when you see it, right?

Collapse
 
koresar profile image
Vasyl Boroviak

Regarding the object composition, have you heard of stampit? stampit.js.org/
Just scroll to the end of the main page.

For clarity. I'm the main maintainer.

Collapse
 
koresar profile image
Vasyl Boroviak

I even have a piece about eater/pooper composition using stampit: medium.com/@koresar/fun-with-stamp...

Collapse
 
jeffmottor profile image
Jeff-Mott-OR

Some comments have been hidden by the post's author - find out more