← All material

Core JavaScript · 6 of 9

Composition Over Inheritance

Inheritance ends up with a base class with shared logic and subclasses overriding bits of it. Therefore each subclass's real behavior is scattered across multiple classes and files.

The old way

class BaseValidator {
  validate(value: string) {
    return this.check(value);
  }
  protected check(value: string) {
    return value.length > 0;
  }
}

class EmailValidator extends BaseValidator {
  protected check(value: string) {
    if (value.includes("@")) {
      return super.check(value);
    }

    return false
  }
}

Compose small functions instead

const notEmpty = (value) => value.length > 0;
const hasAt = (value) => value.includes("@");

const isValidEmail = (value) => notEmpty(value) && hasAt(value);

isValidEmail("a@b.com"); // true

Each function does one thing and can be tested and reused on its own. isValidEmail just calls the checks it needs and combines them with plain && — no generic pipe/compose helper to reach for, no guessing what order functions run in or what shape they're expected to return. There's no hierarchy to climb to see what a given check actually does, and no risk of a subclass's override silently changing behavior a base class assumed elsewhere.