What’s the Deal with Composition Anyway?

omwri
4 min readDec 6, 2019

--

Photo by Isaac Ibbott on Unsplash

Software Engineers, for better or worse, have a knack of using fancy terms to explain simple things. In this blog, I aim to demystify two concepts of software engineering: Functional Composition and Object Composition.

First, let’s look at the origin of the word compose.

Compose comes from the Latin word ‘componere’ which means ‘to put together’.

When a musician composes a symphony, she puts together melodies and lyrics to create a song.

When a writer composes an essay or a book, he puts together words and sentences to tell a story.

Similarly, a software engineer uses composition to put together code to craft applications.

This can be done in two ways:

Functional Composition

Functional Composition is a programming technique where the output of one function is passed as an input to another function.

This makes it easier to create larger functions as collections of smaller functions. It also makes it easy to reuse smaller functions across an application.

const assert = require(“assert”);const addTwo = n => n + 2;const multiplyByThree = n => n * 3;const addTwoAndMultiplyByThree = n => multiplyByThree(addTwo(n));const multiplyByThreeAndAddTwo = n => addTwo(multiplyByThree(n));assert.equal(addTwoAndMultiplyByThree(5), 21);assert.equal(multiplyByThreeAndAddTwo(5), 17)

Consider the above example. We have a function that adds two to a number and another that multiplies it by three. We can write a third function that adds two to a number and multiplies it by three using functional composition. We can also apply the operations in a different order if we need to.

Thus, this technique helps in creating an application with simple, reusable and testable parts.

Object Composition

Before talking about object composition, let’s first understand inheritance.

Inheritance is a standard and well-known technique where an object derives its structure from a blueprint of another object. It can also be implemented as prototypal inheritance where objects derive directly from other objects.

Object Inheritance Example

const assert = require(“assert”);class Drink {  constructor(type) {    this.type = type;  }}
class Tea extends Drink { constructor(flavor) { super(“hot”); this.flavor = flavor }}
class Coffee extends Drink { constructor(type, roast) { super(type); this.roast = roast; }}
const greenTea = new Tea(“green”);
const icedCoffee = new Coffee(“cold”, “dark”);
assert.deepEqual(greenTea, { type: “hot”, flavor: “green”})
assert.deepEqual(icedCoffee, { type: “cold”, roast: “dark”})

Consider the above example where we create a base class Drink and child classes Tea and Coffee that inherit from the base class. Although useful, inheritance comes with a set of problems like:

  1. The child object is tightly coupled with the structure of the parent class. For example, here any object that inherits from Drink has to have a type property.
  2. You get more than what you ask for. The child object has more methods and properties than it needs. Suppose, a new class inherits from Tea but does not need the property flavor. There is no way to exclude this property from the child.
  3. Changes to the parent class can break multiple child classes. Here any changes to the class Drink can potentially break objects of class Tea and Coffee.
  4. The parent-to-child hierarchy can become so large and complex that future enhancements become difficult.

Object composition is a technique that can be used to solve these problems. Here, objects are created by combining primitive data types like strings, integers, booleans etc. This spares us the need from using inheritance.

Let’s rewrite our inheritance code with object composition.

Object Composition Example

const assert = require("assert");const HOT = "hot";const COLD = "cold";const DARK_ROAST = "dark";const GREEN_FLAVOR = "green";const withPour = obj => {return {...obj, pour: () => `Pouring ${obj.flavor}`}}const greenTea = withPour({ type: HOT, flavor: GREEN_FLAVOR });
const icedCoffee = { type: COLD, roast: DARK_ROAST };
assert.deepEqual(icedCoffee, { type: "cold", roast: "dark"})assert.deepEqual(greenTea.pour(), `Pouring green`);

As we see here, we can easily create independent objects with the same structure without creating base classes or inheritance.

We can also add methods to an object as seen with the withPour method and ensure that only certain methods are added to an object.

Ultimately, these are programming styles i.e a preference to write code and there is no objective way to claim that one style is better than another. However, composition does help in keeping your code simple and easy to read. Any software engineer would be well suited to pick the style tuned to the job as long as they have valid reasons for doing so.

There you have it. You know composition now. Pick your style, go forth and build awesome things!

Sources

https://en.wiktionary.org/wiki/compose#Etymology

--

--

omwri
omwri

No responses yet