Every way to vary behaviour
Subclass and override, abstract classes, interfaces, default methods, lambdas, sealed types with a switch, composition and Strategy.
Ask where the variation lives and who adds new cases.
- A fixed, known set of cases, each with different data → a sealed interface of records, and an exhaustive
switchwith patterns. The compiler tells you every place a new case must be handled. - An open set: other code adds new kinds → an interface, one class per kind, and dynamic dispatch.
- One step of an algorithm varies → pass it in: a lambda for a functional interface, or a Strategy object.
- Shared code and shared state among closely related classes → an abstract class. Prefer composition when the relationship isn't truly "is a".
Through inheritance
Section
Inheritance: extends and super
Reusing and extending a superclass.
Section
Overriding and dynamic dispatch
The object's class picks the body; the declared type picks the overload.
Choosing the override at run time from the object's class.
Section
Abstract classes
Shared code plus methods each subclass must supply.
Through interfaces and lambdas
Section
Interfaces
A contract any class can sign.
An interface method with a body, so implementers can skip it.
Example
Comparator<Order> byTotal = (a, b) -> Integer.compare(a.total(), b.total());One abstract method, so a lambda can implement it.
Section
Strategy and Observer
Swapping a step of an algorithm behind an interface.
Through data and a switch
Term
sealed
A type whose subtypes are all listed, so a switch can cover them.
Take a record apart in a case, and let the compiler check you covered every subtype.
Section
Composition versus inheritance
When 'has a' beats 'is a'.