composition-over-inheritance

composition-over-inheritance

Use when tempted to use class inheritance. Use when creating class hierarchies. Use when subclass needs only some parent behavior.

4звезд
0форков
Обновлено 1/23/2026
SKILL.md
readonlyread-only
name
composition-over-inheritance
description

Use when tempted to use class inheritance. Use when creating class hierarchies. Use when subclass needs only some parent behavior.

Composition Over Inheritance

Overview

Favor object composition over class inheritance.

Inheritance creates tight coupling and rigid hierarchies. Composition creates flexible, reusable components that can be mixed and matched.

When to Use

  • Designing relationships between classes
  • Tempted to use extends
  • Class needs behavior from multiple sources
  • Creating "is-a" relationships
  • Building class hierarchies

The Iron Rule

NEVER use inheritance when composition would work.

No exceptions:

  • Not for "it's the OOP way"
  • Not for "is-a relationship"
  • Not for "code reuse via extends"
  • Not for "polymorphism"

Default to composition. Use inheritance only for true type hierarchies.

Detection: The Inheritance Smell

If inheritance feels awkward or forced, use composition:

// ❌ VIOLATION: Inheritance hierarchy
class Animal {
  eat(): void { console.log('Eating'); }
}

class FlyingAnimal extends Animal {
  fly(): void { console.log('Flying'); }
}

class SwimmingAnimal extends Animal {
  swim(): void { console.log('Swimming'); }
}

// Duck needs both fly AND swim - inheritance can't do this cleanly
class Duck extends FlyingAnimal {
  swim(): void { console.log('Swimming'); } // Duplicated!
}

Correct Pattern: Composition with Interfaces

Define capabilities as interfaces, compose them:

// ✅ CORRECT: Composition
interface Flyable {
  fly(): void;
}

interface Swimmable {
  swim(): void;
}

interface Eatable {
  eat(): void;
}

// Reusable behaviors
const flyingBehavior: Flyable = {
  fly() { console.log('Flying'); }
};

const swimmingBehavior: Swimmable = {
  swim() { console.log('Swimming'); }
};

const eatingBehavior: Eatable = {
  eat() { console.log('Eating'); }
};

// Compose what you need
class Duck implements Flyable, Swimmable, Eatable {
  fly = flyingBehavior.fly;
  swim = swimmingBehavior.swim;
  eat = eatingBehavior.eat;
}

class Fish implements Swimmable, Eatable {
  swim = swimmingBehavior.swim;
  eat = eatingBehavior.eat;
}

class Bird implements Flyable, Eatable {
  fly = flyingBehavior.fly;
  eat = eatingBehavior.eat;
}

Why Inheritance Fails

Problem Example
Diamond problem Duck needs Flying AND Swimming
Tight coupling Child knows parent internals
Rigid hierarchy Can't change parent without breaking children
Forced inheritance Gets methods it doesn't need
Fragile base class Parent changes break all children

Why Composition Wins

Benefit Example
Flexible Mix any behaviors together
Loose coupling Components don't know each other
Easy testing Mock individual behaviors
Runtime changes Swap behaviors dynamically
No hierarchy lock-in Add new combinations freely

Pressure Resistance Protocol

1. "It's the OOP Way"

Pressure: "Object-oriented programming uses inheritance"

Response: Modern OOP favors composition. Inheritance is overused.

Action: Use interfaces + composition. It's still OOP.

2. "It's an Is-A Relationship"

Pressure: "A Duck IS-A Bird, so it should extend Bird"

Response: "Is-a" often becomes "has-a" when requirements change. Composition handles both.

Action: Model as "has behaviors" not "is a type".

3. "Code Reuse via Extends"

Pressure: "I need the parent's methods"

Response: Composition provides better code reuse without coupling.

Action: Extract shared behavior into composable units.

4. "Polymorphism Requires Inheritance"

Pressure: "I need to treat different types uniformly"

Response: Interfaces provide polymorphism without inheritance.

Action: Define interface, have classes implement it.

Red Flags - STOP and Reconsider

If you notice ANY of these, use composition instead:

  • extends keyword in your code
  • Class hierarchy deeper than 2 levels
  • Child class overriding parent methods
  • "Diamond problem" - needs multiple parents
  • Subclass doesn't use all parent methods
  • Changing parent breaks children
  • Hard to test without instantiating parent

All of these mean: Refactor to composition.

When Inheritance IS Appropriate

Use inheritance only when:

  • True type hierarchy (rarely)
  • Framework requires it (React class components, etc.)
  • Extending library classes you don't control

Even then, keep hierarchy shallow (max 2 levels).

Quick Reference

Inheritance Composition
class Dog extends Animal class Dog implements Animal + behavior injection
Rigid hierarchy Flexible composition
Single parent only Multiple behaviors
Tight coupling Loose coupling
Changes cascade Changes isolated

Common Rationalizations (All Invalid)

Excuse Reality
"It's the OOP way" Modern OOP prefers composition.
"It's an is-a relationship" "Has behavior" is more flexible.
"Need parent's methods" Compose the behavior instead.
"Polymorphism needs it" Interfaces provide polymorphism.
"Less code with extends" More flexibility with composition.
"I noted it's problematic" Don't do it if it's problematic.

The Bottom Line

Compose behaviors. Don't inherit them.

When designing classes: define interfaces for capabilities, create composable behaviors, inject what each class needs. Use extends only as last resort.

You Might Also Like

Related Skills

coding-agent

coding-agent

179Kdev-codegen

Run Codex CLI, Claude Code, OpenCode, or Pi Coding Agent via background process for programmatic control.

add-uint-support

add-uint-support

97Kdev-codegen

Add unsigned integer (uint) type support to PyTorch operators by updating AT_DISPATCH macros. Use when adding support for uint16, uint32, uint64 types to operators, kernels, or when user mentions enabling unsigned types, barebones unsigned types, or uint support.

at-dispatch-v2

at-dispatch-v2

97Kdev-codegen

Convert PyTorch AT_DISPATCH macros to AT_DISPATCH_V2 format in ATen C++ code. Use when porting AT_DISPATCH_ALL_TYPES_AND*, AT_DISPATCH_FLOATING_TYPES*, or other dispatch macros to the new v2 API. For ATen kernel files, CUDA kernels, and native operator implementations.

skill-writer

skill-writer

97Kdev-codegen

Guide users through creating Agent Skills for Claude Code. Use when the user wants to create, write, author, or design a new Skill, or needs help with SKILL.md files, frontmatter, or skill structure.

Implements JavaScript classes in C++ using JavaScriptCore. Use when creating new JS classes with C++ bindings, prototypes, or constructors.

Creates JavaScript classes using Bun's Zig bindings generator (.classes.ts). Use when implementing new JS APIs in Zig with JSC integration.