SOLIDmedium3-5 years

A teammate writes `class ReadOnlyOrderRepository extends JpaOrderRepository { public Order save(Order o) { throw new UnsupportedOperationException(); } }` to enforce that a reporting service can't accidentally write. It compiles cleanly and passes code review. What's actually wrong with it, and what should the fix look like?

It's a Liskov substitution violation: ReadOnlyOrderRepository is-a JpaOrderRepository by the type system, so every service that accepts a JpaOrderRepository and calls save on it is now allowed, by the compiler, to be handed this one — and it throws at run time, in whatever code path happens to call save, which is exactly the path nobody's test exercised. ReadOnlyOrderRepository promised the full JpaOrderRepository contract by extending it and delivers less: a caller cannot tell from the type alone that save is a trap. The fix the lesson gives directly: when you find yourself wanting to remove a capability from a subtype rather than add one, the relationship is not inheritance at all — it should be a separate, narrower interface (OrderReader, say, with just the read methods and no save) that the read-only service depends on, so the type system itself makes it impossible to call save on something that was never meant to support it, rather than making it compile and fail later.

The lesson behind it →
More on SOLID