RSS Amplifier

Universal Set · Jan 31, 2024

Demystifying Composition

0
Sign in to vote or save

Quentin Lintz · Universal Set

The phrase “favor object composition over class inheritance”1 has permeated design discussion meetings since the conception of object-oriented approaches to creating software. If it’s favored enough to be featured in the first chapter of Design Patterns, I think it’s worth fully understanding what it means and how it’s used.

In a literal sense, the dictionary definition of “Composition” is: “arrangement into specific proportion or relation and especially into artistic form.”2 This appears to advocate for code organization for the sake of reuse — an admiral design philosophy. Design Patterns states that composition aims to assemble objects to attain more complex functionality. This has encapsulation merits, too, as one object will have a reference to multiple other objects.

Let’s try creating a squirrel with our current understanding of composition. The squirrel is a creature, so it should compose scurry and climb movements, right?

const creature = (state) => {
  return {
    startScurrying: () => {
      if (!state.isClimbing) {
        state.isScurrying = true;
      }
    },
    startClimbing: () => {
      if (!state.isScurrying) {
        state.isClimbing = true;
      }
    },
  };
};
const createSquirrel = () => {
  let state = {
    isScurrying: false,
    isClimbing: false,
  };
  return Object.assign(state, creature(state));
};
const squirrel = createSquirrel();
console.log(squirrel.isClimbing); // false
squirrel.startClimbing();
console.log(squirrel.isClimbing); // true

Here, we’re composing the scurry and climb behaviors by combining them together. However, this is not a composition by way of delegation! Copying objects with Object.assign leads to fragile design and unintended effects. While this is an attempt to combine or “compose,” it’s actually an expression of inheritance.

Despite the dictionary definition, in the context of programming, composition means delegating functionality to dependent objects. Composition is often compared to inheritance to define relationships between classes. This should serve as a rule for when to use either:

  • Inheritance: “is a” e.g., a squirrel is an animal

  • Composition: “has a” e.g., a squirrel has [a] movement pattern

Let’s try to fix our original example to compose the squirrel. A squirrel “has a” movement strategy, which we instantiate and pass as an argument to the new squirrel.

class Squirrel {
  constructor(movementStrategy) {
    this.movementStrategy = movementStrategy;
    this.state = {
      isClimbing: false,
      isScurrying: false,
    };
  }
  scurry() {
    this.movementStrategy.scurry(this);
  }
  climb() {
    this.movementStrategy.climb(this);
  }
}
class Movement {
  scurry(squirrel) {
    if (!squirrel.state.isClimbing) {
      squirrel.state.isScurrying = true;
    }
  }
  climb(squirrel) {
    if (!squirrel.state.isScurrying) {
      squirrel.state.isClimbing = true;
    }
  }
}
const squirrel = new Squirrel(new Movement());
console.log(squirrel.state.isClimbing); // false
squirrel.climb();
console.log(squirrel.state.isClimbing); // true

Notice now how squirrel.climb(); delegates functionality to the underlying Movement class. Through delegation, we can compose behaviors at runtime so we can change the movement strategy based on—for instance—behavior based on the terrain the squirrel is scurrying/climbing on.

Composition is an important pattern to grasp so we may design for change.

Another critical improvement is that this gives us loose coupling, which makes testing easier! Through dependency injection, we can pass a mock or stub implementation Movement to isolate logic in Squirrel.

In summary, the benefits of composition include:

  • Loose coupling

  • Design flexibility

  • Easier unit testing

Read more about composition vs. inheritance.

Cover image source: Craiyon

1

Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, Design Patterns: Elements of Reusable Object-Oriented Software (Addison-Wesley, 1994), 20.

Read the original on universalset.substack.com

Comments

Nothing yet. Say the first thing.

    Sign in to join the conversation.