Week 3 Day 3 — Composition vs Inheritance
Goal
Today I want the design default: compose objects, inherit types sparingly.
Main questions:
- What is composition?
- What is inheritance for, if not reuse?
- Why is “extends for reuse” a trap?
- How does Dependency Injection implement composition?
- What is a strategy vs a subclass?
1. Two ways to reuse
Inheritance (is-a): Child is a Parent. I get Parent’s API and can override.
Composition (has-a): An object holds another object and delegates.
public class OrderService {
private final OrderRepository orders; // has-a
private final PaymentGateway payments; // has-a
private final Clock clock; // has-a
}
OrderService is not a repository. It uses one.
Memory sentence:
Inheritance is is-a. Composition is has-a. Spring wiring is has-a.
2. Why inheritance-for-reuse hurts
public class VipOrderService extends OrderService {
@Override
public void place(CreateOrderRequest request) {
discount();
super.place(request);
}
}
Problems:
VipOrderServiceis stuck in the parent constructor and parent fields.- Tests for VIP still construct the whole parent graph.
- A third variant (
CorporateOrderService) explodes into a tree. - Parent changes break children that depended on internals (
protectedfields). - Spring now has two
@Serviceclasses of related types — injection gets ambiguous.
Composition version:
public class OrderService {
private final PricingPolicy pricing;
public void place(CreateOrderRequest request) {
Money price = pricing.price(request);
// save, pay
}
}
public interface PricingPolicy {
Money price(CreateOrderRequest request);
}
RegularPricing, VipPricing, CorporatePricing are substitutes. OrderService does not change.
3. When inheritance is the right tool
Use extends when the type is real and stable:
IllegalArgumentException extends RuntimeExceptionArrayListis-aAbstractListis-aList(library design)- a closed domain hierarchy I will switch on with pattern matching later (
sealedtypes in Week 9)
Ask: “Would I pass a VipOrderService to a method that expects OrderService and be happy?” If the only reason to extend is to steal methods, compose instead.
The Gang-of-Four line is still the right interview sentence:
Favor composition over inheritance.
4. Delegation and wrapping
Composition often looks like wrapping:
public class LoggingGateway implements PaymentGateway {
private final PaymentGateway inner;
public LoggingGateway(PaymentGateway inner) {
this.inner = inner;
}
public void charge(Money amount) {
log(amount);
inner.charge(amount);
}
}
That is a decorator: same interface, extra behavior, no subclass of Stripe. Spring AOP proxies are this idea at runtime: a wrapper of the same type that runs extra code, then delegates.
5. Spring connection
The container is a composition engine.
@Service
public class OrderService {
public OrderService(
OrderRepository orders,
PaymentGateway payments,
PricingPolicy pricing) { ... }
}
I do not new the collaborators. I declare has-a relationships. Spring builds the graph.
Inheritance still appears:
- my class extends nothing interesting
- a proxy may extend my class to wrap methods
@SpringBootApplicationclasses do not form a domain hierarchy
If I feel I need extends OrderService, I probably need a new injected collaborator.
6. Common traps
Trap 1: BaseService with protected repositories for all children.
A god superclass. Every test loads everything. Split collaborators.
Trap 2: Inheritance to share two helper methods.
A package-private utility or a small collaborator is enough.
Trap 3: Decorator that changes the contract (throws new exceptions, swallows errors).
Broken substitution.
Trap 4: “Spring inheritance” — @Component on a parent class.
Subclass beans can get unexpected extra components. Prefer composition of beans, not component class hierarchies.
Practice Questions and Answers
Question 1
Composition vs inheritance?
Answer:
Inheritance is is-a: a subtype that can stand in for the parent. Composition is has-a: an object holds collaborators and delegates. I use inheritance for real type hierarchies. I use composition for reuse of behavior. Spring DI is composition.
Question 2
Why is extending a concrete service to make a “VIP” variant a poor default?
Answer:
The child is coupled to the parent’s constructor, fields, and bean identity. New variants grow a class tree. A PricingPolicy collaborator keeps one OrderService and substitutes policy objects.
Question 3
How is a Spring proxy related to composition?
Answer:
The proxy is a wrapper with the same type as the target. It runs extra logic (transaction, security), then delegates to the real object. That is decorator-style composition, even when CGLIB implements it by subclassing.
Question 4
When would I still use inheritance in backend code?
Answer:
Exception types, a small sealed domain hierarchy, or a true is-a relationship I would happily substitute. Not to share a repository field.
Question 5
What does “favor composition over inheritance” mean in a Spring interview?
Answer:
I inject collaborators through the constructor instead of extending a base service. I add behavior by wrapping or by new implementations of an interface. Inheritance is for types, not for grabbing code.
Memory sentences
Inheritance is is-a. Composition is has-a.
If I extend a service to change a policy, I wanted a collaborator.
Spring wires has-a. Proxies wrap the same type.