Liskov Substitution
A subtype must preserve the expectations established by its base contract. Matching method names is insufficient if an implementation strengthens preconditions, weakens guarantees, or breaks invariants.
What you’ll understand
- A subtype must preserve the expectations established by its base contract. Matching method names is insufficient if an implementation strengthens preconditions, weakens guarantees, or breaks invariants.
- Check every withdrawal condition before mutating the account, then return the agreed success flag.
- Evaluate the tradeoff: Contracts must be explicit enough to test. If a subtype cannot honor an operation, model a smaller capability instead of returning surprising values or throwing for ordinary base-class inputs.
Useful first: Classes and Objects
The idea and when to recognize it
A subtype must preserve the expectations established by its base contract. Matching method names is insufficient if an implementation strengthens preconditions, weakens guarantees, or breaks invariants.
- Requirement: A caller uses account.withdraw and expects a rejected withdrawal to leave the balance unchanged.
- Change to anticipate: Apply a reserve policy without allowing a rejected operation to consume money.
- Design goal: Check every withdrawal condition before mutating the account, then return the agreed success flag.
Start with the concrete problem
A caller uses account.withdraw and expects a rejected withdrawal to leave the balance unchanged.
Apply a reserve policy without allowing a rejected operation to consume money.
| Direct implementation | Revised design |
|---|---|
| Mutating the balance before discovering that a reserve rule rejects the withdrawal violates the no-change-on-failure guarantee. | Check every withdrawal condition before mutating the account, then return the agreed success flag. |
| The baseline case can hide the problem. Compare the extension and boundary fixtures before accepting this design. | Contracts must be explicit enough to test. If a subtype cannot honor an operation, model a smaller capability instead of returning surprising values or throwing for ordinary base-class inputs. |
Responsibilities and contracts
Check every withdrawal condition before mutating the account, then return the agreed success flag.
- Account: Promises unchanged state on rejection. Public operations: withdraw(amount)
- ReserveAccount: Adds a configured reserve within the agreed policy. Public operations: withdraw(amount)
Read the design
Select a participant to inspect its responsibility and public operations. Follow the labeled relationships; an arrow indicates direction, while the legend distinguishes ownership, substitution, and temporary use.
Preparing the design walkthrough…
Follow the interaction
Choose a supplied workload and compare the direct and revised implementations. Step through the same operation in both languages. Object values are recorded after each highlighted statement; previous steps remain unchanged.
Preparing the design walkthrough…
Implementation and expected output
Read the complete example alongside its input and expected output. The implementation selector changes the displayed program; the language selector preserves your current scenario and playback position.
Preparing the design walkthrough…
Try the idea in another setting
A storage reader subtype must preserve the same end-of-input behavior as the reader contract.
- Name the new client contract before adapting the example.
- Identify which state belongs to each object and who controls its lifetime.
- Write one normal case and one boundary case before extending the design.
Tradeoffs and interview takeaways
Contracts must be explicit enough to test. If a subtype cannot honor an operation, model a smaller capability instead of returning surprising values or throwing for ordinary base-class inputs.
Common mistake: Mutating the balance before discovering that a reserve rule rejects the withdrawal violates the no-change-on-failure guarantee.
- Explain the choice: Check every withdrawal condition before mutating the account, then return the agreed success flag.
- Challenge your choice: Contracts must be explicit enough to test. If a subtype cannot honor an operation, model a smaller capability instead of returning surprising values or throwing for ordinary base-class inputs.
- Describe a requirement that would make the simpler implementation sufficient.
Design challenge and knowledge check
Preparing the design walkthrough…