The full picture
sealed interface PaymentResult permits Approved, Declined, Pending {}
record Approved(String authCode, Instant at) implements PaymentResult {}
record Declined(DeclineReason reason, boolean retryable) implements PaymentResult {}
record Pending(String checkUrl) implements PaymentResult {}
String describe(PaymentResult r) {
return switch (r) { // NO default: exhaustive!
case Approved(String code, _) -> "OK " + code;
case Declined(var reason, true) -> "Retry: " + reason;
case Declined(var reason, false) -> "Hard decline: " + reason;
case Pending(String url) -> "3DS at " + url;
};
} // add 'Refunded' to permits -> THIS switch stops compiling. Feature, not bug.- Record patterns deconstruct nested data (
case Line(Point(var x1, var y1), Point(var x2, var y2))); guards add conditions (case Declined d when d.retryable()). - Sealed ≠ final: permitted subclasses choose
final,sealed, ornon-sealed(escape hatch — use rarely). - Design guidance: model states and events as sealed hierarchies (order states, domain events, parser nodes); keep behavior-rich strategies as open interfaces — closed data, open behavior.
- Exhaustiveness + no default is the whole point: writing
default -> throwthrows away the compiler's help.