Card 07/ 09

ComparisonDifficulty: Advanced1 min

A Polymorphic Call Against a Chain of instanceof

Both shapes work. One puts the behaviour on the types and one puts it in the caller, and which is right depends on something specific: whether the set of types or the set of operations changes more often.

Two ways to vary behaviour by type, on what each change costs
Compared onA method on each typeA chain of instanceof in the caller
Adding a new typeOne new class; nothing else is touchedEvery chain must be found and extended
Adding a new operationEvery class must gain a methodOne new method in one place
Missing a caseThe compiler refuses the classFalls through the chain, silently
Where the behaviour livesBeside the data it usesAway from it, in the caller

The decision rule. When new types arrive more often than new operations — payment methods, shapes, report formats — put the method on the type. When the types are fixed and closed and the operations keep coming, the chain in the caller is the honest shape.

Java 21 makes the second case safe rather than merely tolerable. A switch with pattern labels over a sealed type is checked for exhaustiveness by the compiler, so the silent fall-through in row three stops being possible.

java
String describe(Shape s) {
    return switch (s) {
        case Circle c -> "circle of " + c.radius();
        case Square q -> "square of " + q.side();
        case Line l   -> "line";
    };
}
no default, and it compiles only because Shape is sealed over exactly these three

What has not changed is the first row. Adding a fourth shape still means finding this method, and the compiler will now make sure you do. A build that refuses is a much better failure than a cast that throws on one record in production.