Composition over Inheritance

In functional programming, you usually don't use classes. But that begs the question: How can you model complex structures without using inheritance?

The answer is quite simple: We use a pattern called Composition.

Todo-App using OOP

As an example, let's look at a Todo-App. In particular, assume we have location-based todos (reminding you every time you are at that location) and time-based todos (reminding you on a specific date).

interface Todo { message : string; }
interface RemindableAtLocation { 
    location : string;
    remindAtLocation(string) : void;
}
interface RemindableOnDate {
    date : Date;
    remindOnDate(Date) : void;
}
class BuyFlowersForMothersday extends 
    Todo, RemindableAtLocation, RemindableOnDate 
{    
    constructor(flowerShop: string, mothersday:Date) {
      this.message = "Buy flowers for your Mom";
      this.location = flowerShop;
      this.date = mothersdays;
    }
    remindAtLocation(currentLocation:string) : void {
      if (this.location === currentLocation)
        sendEmail(this.message);
    }
    remindOnDate(currentDate:Date) : void {
      if (this.date === currentDate)
        sendEmail(this.message);
    }
}

const today = new Date();
const task = new BuyFlowersForMothersday("Bella Flora",today);
task.remind("Bella Flora")
task.remind(today)

In OOP we define a class BuyFlowersForMothersday, taking a location and a date. This class reminds you every time you visit the location and on that date.

This approach has the downside, that all fields of the class are hidden behind interfaces and the behaviour is hardcoded into the class. How would you add a way to get reminded via SMS without touching the existing code?

Rewrite in FP using Composition

Now let's rewrite the example using composition. In functional programming, we use records and move functions outside of classes.

interface Todo { todoMessage : string; }
interface RemindableLocation { remindableLocation : string; }
interface RemindableDate { remindableDate : Date; }
interface BuyFlowersForMothersday {
  todo : Todo,
  remindableLocation : RemindableLoation,
  remindableDate : RemindableDate
}
function remindAtLocation(
  currentLocation:string,
  remindableLocation:RemindableLocation,
  todo:Todo
):void {
  if (currentLocation === remindableLocation.remindableLocation)
    sendEmail(todo.todoMessage)
}
function remindOnDate(
  currentDate:Date,
  remindableDate:RemindableDate,
  todo:Todo
):void {
  if (currentDate === remindableDate.remindableDate)
    sendEmail(todo.todoMessage)
}


const today = Date();
const task : BuyFlowersForMothersday = 
  { todo : { todoMessage : "Buy flowers for your Mom" },
    remindableLocation : { remindableLocation : "Bella Flora" },
    remindableDate : { remindableDate : today }
  } 
remindAtLocation("Bella Flora",todo.remindableLocation,task.todo) 
remindOnDate(today,todo.remindableDate,task.todo)

The result is composed of the smallest components, separating data from logic.

Our BuyFlowersForMothersday task is composed of a Todo, a RemindableLocation and a RemindableDate. These components are independent of each other and also can be used (and tested) independently.

Anyone can create a record. Therefore, the field names of the record need to be very clear about which data they include. This ensures that we can only call remindAtLocation if we have some remindableLocation.

Benefits

We never specified, that the remindableLocation has to come from a todo, because it does not have to. As long as you got it from somewhere, you are able to call the function. This flexibility is the key strength of composition.

You can easily add a way to get notified via SMS without needing to change anything.

Downsides

Composition generally leads to more code (though it's often nicer to read).

There are also some very rare cases where you actually need inheritance. I only ever experienced this when using foreign libraries.