Zum Hauptinhalt springen

Week 3 Day 3 — Composition vs Inheritance

Goal

Today I want the design default: compose objects, inherit types sparingly.

Main questions:

  1. What is composition?
  2. What is inheritance for, if not reuse?
  3. Why is “extends for reuse” a trap?
  4. How does Dependency Injection implement composition?
  5. 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:

  • VipOrderService is 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 (protected fields).
  • Spring now has two @Service classes 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 RuntimeException
  • ArrayList is-a AbstractList is-a List (library design)
  • a closed domain hierarchy I will switch on with pattern matching later (sealed types 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
  • @SpringBootApplication classes 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.

Next: Week 3 Day 4 — SOLID in a Small Backend