Is-a is a relationship of type — modeled with inheritance (a ). is a relationship of — modeled with (a ). Choosing the right one is a core modeling decision.
Is-a is a relationship of type — modeled with inheritance (a ). is a relationship of — modeled with (a ). Choosing the right one is a core modeling decision.
CarVehicleCarEngine// IS-A → inheritance
class Vehicle { void move() {} }
class Car extends Vehicle { } // a Car IS A Vehicle
// HAS-A → composition
class Engine { void start() {} }
class Car2 {
private Engine engine = new Engine(); // a Car HAS AN Engine
void start() { engine.start(); } // delegate to the part
}
Ask: "Is X a kind of Y, or does X have/use a Y?"
A Dog IS-A Animal → inheritance ✅
A Car HAS-A Engine → composition ✅
A Square IS-A Shape → inheritance ✅
A Manager HAS Employees → composition (a list) ✅
A Stack HAS-A list (not IS-A) → composition (see earlier pitfall) ✅
People reach for inheritance to reuse code, even when the relationship is really has-a. If you'd never substitute the subclass for the base everywhere, it's probably not is-a — use composition.
This distinction is the practical decision rule behind "favor composition over inheritance": pick the relationship that's true, not the one that saves a few lines.
Getting it right keeps hierarchies shallow and honest, and it prevents Liskov violations where a "subtype" can't actually stand in for its parent.