While Inheritance is a powerful tool, overusing it creates deep, rigid class hierarchies that are difficult to modify and test. The principle of "Favor Composition over Inheritance" (from the Gang of Four's Design Patterns book, 1994) suggests that you should build complex behavior by combining simple, independent objects rather than by creating deep inheritance trees.
Imagine you are building a game and need various character types. You start with inheritance:
Character
āāā Warrior (can fight)
āāā Archer (can shoot)
āāā Mage (can cast spells)
This works initially. But then requirements change:
Paladin that can fight AND cast spells.Ranger that can shoot AND fight.With single inheritance (Java), Paladin cannot extend both Warrior AND Mage. You're stuck. You either duplicate code or create an increasingly tangled hierarchy.
This is known as the Fragile Base Class Problem. Changes to a parent class can have unintended cascading effects on dozens of child classes deep in the hierarchy.
Instead of defining abilities through an inheritance chain, you define abilities as independent, self-contained objects and compose them into a character.
// Independent ability components
class FightAbility {
void fight() { System.out.println("Swinging sword!"); }
}
class ShootAbility {
void shoot() { System.out.println("Firing arrow!"); }
}
class MagicAbility {
void castSpell() { System.out.println("Casting fireball!"); }
}
// Characters are COMPOSED of abilities
class Paladin {
private FightAbility fighter = new FightAbility();
private MagicAbility mage = new MagicAbility();
void fight() { fighter.fight(); }
void castSpell() { mage.castSpell(); }
}
class Ranger {
private FightAbility fighter = new FightAbility();
private ShootAbility archer = new ShootAbility();
void fight() { fighter.fight(); }
void shoot() { archer.shoot(); }
}
Now, adding a new character type (DarkKnight that fights and casts spells) requires zero modification of existing code. You just compose the existing ability objects in a new configuration.
The key mental model:
Dog IS-A Animal. This is a permanent, defining identity.Car HAS-A Engine. A Paladin HAS-A FightAbility. These are interchangeable components.Use IS-A when the relationship is truly definitional and permanent. Use HAS-A when the relationship is about capability, configuration, or behavior that might change.
Composition is most powerful when combined with Dependency Injection (DI). Instead of the Paladin class internally creating its own FightAbility object (tight coupling), the ability is injected from the outside:
class Paladin {
private FightAbility fighter;
private MagicAbility mage;
// Dependencies are INJECTED via the constructor
Paladin(FightAbility fighter, MagicAbility mage) {
this.fighter = fighter;
this.mage = mage;
}
}
This makes the Paladin class incredibly easy to test. In a unit test, you can inject a MockFightAbility that records calls instead of actually fighting, making assertions trivial.