Javascript Composition Tutorial

In this Javascript tutorial we learn how to design loosely coupled applications with composition and why you should favor it above inheritance.

We cover how to instantiate a class within another class and how to access class members through multiple objects.

Lastly we take a look at the pro's and cons of inheritance and composition.

Here's a table of contents of what you'll learn in this lesson:
(click on a link to skip to its section)

Let's jump right in.

What is Composition

Composition is a type of relationship between classes where one class contains another. Composition should be favored above inheritance because it’s more flexible and allows us to design loosely coupled applications.

As an example, let’s consider an application that converts a video from one format to another. Typically, we would create a converter class that contains the details of the file and the functionality to convert it.

Then, we could have another class that inherits from the converter and sends a notification to the user when it’s done, pulling details like the name, size etc. from the parent converter class.

But what if we wanted to add audio file conversion to our application. We would have to write another notification class, this time inheriting from the audio converter.

The first problem is that we have two child classes that essentially do the same thing. The second problem is that the children heavily depend on their respective parents. If we needed to change something in the parent converter classes, we could break the functionality of the children.

The solution is composition. Where inheritance can be thought of as an “is-a” relationship, composition can be thought of as a “has-a” relationship. Instead of creating a child class that inherits functionality from a parent class, we create stand-alone classes that contain other classes.

Simply put, we create an object instance of class inside another class.

Composition

We can convert any inheritance relationship to composition. To create this association between classes we instantiate a new class object inside the constructor of the class we want to use it.

Syntax:
class Class_1 {}

class Class_2 {

  constructor() {
    // create object of Class_1
    this.object_name = new Class_1();
  }

}

It’s similar to how we construct properties, except this time we create a new instance object of another class.

Example:
<script>

  class Pushable {

    send_push_message() {
      document.write("Your file has been converted successfully.<br>");
    }
  }

  class Converter {

    constructor() {
      this.push = new Pushable();
    }

    convert() {
      document.write("Converting...<br>");
      return true;
    }
  }

</script>

The Converter class now has access to the functionality to send_push_message() without inheriting from the Pushable class.

Functionality classes like Pushable are often named with “able”, “can” or “has”. Like Flyable, CanDrive, HasFood etc.

How to access an object in a class

To access an object in a class we go another level deeper with dot notation. Let’s see the syntax first, then break it down.

Syntax:
class Class_1 {

  method() {}
}

class Class_2 {

  constructor() {
    this.class_1_obj = new Class_1();
  }

}

var class_2_obj = new Class_2();

class_2_obj.class_1_obj.method();

Before we can access anything in Class_2, we have to create an object of it. Once we have the class_2_obj, we can access the class_1_obj with dot notation.

Because class_1_obj is an object itself, we have to use another level of dot notation to access its method(). An example will illustrate it better.

Example:
<script>

  class Pushable {

    send_push_message() {
      document.write("Your file has been converted successfully.<br>");
    }
  }

  class Converter {

    constructor() {
      this.push = new Pushable();
    }

    convert() {
      document.write("Converting...<br>");
      return true;
    }
  }

  var c1 = new Converter();

  c1.convert();
  c1.push.send_push_message();

</script>

In the example above we access the push object that was created in the Converter class constructor, from the c1 object. From there, we simply access the send_push_message() through dot notation.

Favor Composition over Inheritance

You might be asking, if composition only gives us indirect access, why should we use it?

Well, the problem with inheritance is that it can be abused easily, which could lead to a large hierarchy of classes that are dependent on one another.

A large hierarchy of classes is fragile, if we change a class at the top of the hierarchy, any class that depend on it is affected and may need to be changed as well.

As an example, let’s consider a class called Animal and some child classes that inherit from it.

Example:
<script>

  class Animal {

    eat() {
      document.write("Eating...<br>");
    }

    walk() {
      document.write("Walking...<br>");
    }
  }

  class Dog extends Animal {}
  class Cat extends Animal {}

</script>

The Dog and the Cat can both eat() and walk(). But what if we add a Fish, the hierarchy will need to change.

We would need something like a Mammal class that inherits from Animal, and then Dog and Cat can inherit from Mammal. Fish will have to inherit from Animal but be separate from Mammal and depending on what we want to do, Animal may have to change.

Now let’s consider that instead of is-a animal, the child classes now has-a animal.

Example:
<script>

  class Animal {

    eat() {
      document.write("Eating...<br>");
    }
  }

  class Walkable {

    walk() {
      document.write("Walking...<br>");
    }
  }

  class Swimmable {

    swim() {
      document.write("Swimming...<br>");
    }
  }

  class Dog {

    constructor() {
      this.animal = new Animal();
      this.walkable = new Walkable();
      this.swimmable = new Swimmable();
    }
  }

  class Fish {

    constructor() {
      this.animal = new Animal();
      this.swimmable = new Swimmable();
    }
  }

</script>

In the example above we added a class for any animal that can walk, and a class for any animal that can swim.

The Cat and Dog classes can implement both Walkable and Swimmable, and the Fish class can implement Swimmable. If we wanted to add a bird, we could simply add a Flyable class, and the bird could implement Walkable, Swimmable and Flyable.

The application is now loosely coupled, as each class stands on its own and is not dependent on another class. Let’s look at a full example.

Example:
<script>

  class Walkable {

    walk() {
      document.write("Walking...<br>");
    }
  }

  class Swimmable {

    swim() {
      document.write("Swimming...<br>");
    }
  }

  class Flyable {

    fly() {
      document.write("Flying...<br>");
    }
  }

  class Fish {

    constructor() {
      this.swimmable = new Swimmable();
    }
  }

  class Bird {

    constructor() {
      this.walkable = new Walkable();
      this.swimmable = new Swimmable();
      this.flyable = new Flyable();
    }
  }

  document.write("Nemo the fish's activities:<br>");
  var nemo = new Fish();
  nemo.swimmable.swim();

  document.write("Tweety the bird's activities:<br>");
  var tweety = new Bird();
  tweety.walkable.walk();
  tweety.swimmable.swim();
  tweety.flyable.fly();

</script>

Composition vs Inheritance: Pro’s & Cons

Inheritance:

  • Pro’s: Reusable code, easy to understand
  • Cons: Tightly coupled, fragile, can be abused

Composition:

  • Pro’s: Reusable code, flexibility, loosely coupled
  • Cons: Harder to understand

Please note that we don’t mean inheritance is a bad thing, you will still need and use inheritance. Composition is just an alternative that we need to consider before using inheritance.

Summary: Points to remember

  • Composition is instantiating and accessing a class inside another instead of inheriting from it.
  • Any inheritance relationship can be converted into composition.
  • A class that’s instatiated inside another must have the this keyword to refer to the calling object.
  • Access to the inner object is done via dot notation, multiple levels deep.
  • We should typically try to favor composition over inheritance.