Inheritance means a child class gets the parent's behavior for free. The child can keep it or change it (override it). Use it only when "a child is a parent" is always true.
Composition means one object keeps another object inside it as a helper. The object has a helper and hands work to it (delegating). It is not a special version of the helper.
When to use which
- Inheritance (“is-a”): Use when the subclass is a more specific kind of the base type, and you need polymorphism (code for the parent works with any child) (e.g.
Dogis anAnimal,CreditCardis aPaymentMethod). - Composition (“has-a”): Use when one object uses another to do part of its job, but is not a kind of it (e.g.
Carhas anEngine,PaymentServicehas aPaymentMethod).
Prefer composition when
- You want to reuse behavior without exposing the helper's methods.
- You need to swap implementations while the program runs (e.g. inject different payment methods).
- The relationship is has-a rather than is-a (e.g. “a car has an engine”).
- You want to avoid deep inheritance and fragile base classes (a small parent edit breaks many children).
Prefer inheritance when
- There is a clear is-a relationship and you need subtype polymorphism.
- Subclasses keep every promise of the base type (Liskov Substitution: a child works wherever the parent does).
- You need override and override-only extension points.
Example (composition)
You pass the payment method in through the constructor, and pay hands the work to it. You can pass a CreditCard or a Upi without changing this class. This is simplified. The repo's PaymentService stores many methods in a map.
// Composition: PaymentService HAS-A PaymentMethod (injected)
class PaymentService {
private final PaymentMethod method;
PaymentService(PaymentMethod method) {
this.method = method;
}
void pay() {
method.pay();
}
}Example (inheritance)
CreditCard extends Card, and Card implements PaymentMethod, so a CreditCard fits anywhere a PaymentMethod is expected.
// Inheritance: CreditCard IS-A Card, and Card IS-A PaymentMethod
class CreditCard extends Card {
@Override
public void pay() { /* ... */ }
}Summary
| Aspect | Inheritance | Composition |
|---|---|---|
| Relationship | Is-a | Has-a |
| Flexibility | Fixed at compile time | Can swap at runtime |
| Coupling | Tighter (subclass depends on base) | Looser (depends on interface) |
| Use when | True subtype, polymorphism | Reuse + encapsulation |