We define the abstract base class Shape with methods to set width and height and to calculate area. This sets the contract that all shapes must follow.
💡 Defining the base class establishes the expected interface and behavior that subtypes must honor.
⚠ LSP violation: Square does not behave as Rectangle
Key Takeaways
✓ Subtypes must preserve the behavioral contract of their base types to satisfy the Liskov Substitution Principle.
This is hard to see from code alone because method signatures may match but behavior can differ subtly.
✓ Violating the contract leads to unexpected results when subtypes are used polymorphically.
Visualizing the substitution and its effect on state and output clarifies why this is problematic.
✓ Designing subtypes requires careful attention to not only interface but also semantics and side effects.
The trace shows concretely how Square's setters break expectations, a subtle but critical insight.
Practice
(1/5)
1. You need to design a system where user actions can be executed, undone, and redone, while also supporting queuing of requests and logging of all operations. Which design approach best fits this requirement to ensure loose coupling between the invoker and the receiver and to enable undo/redo functionality?
easy
A. Implementing the Command Pattern where each action is encapsulated as an object with execute and undo methods, and the invoker manages undo and redo stacks.
B. Using a simple function call approach where each action directly modifies the receiver's state without encapsulation.
C. Applying a greedy algorithm that chooses the next best action to execute without storing history.
D. Using a dynamic programming approach to store all possible states and transitions for undo/redo.
Solution
Step 1: Identify the need for encapsulation of actions
The problem requires actions to be undoable and redoable, which demands encapsulating each action's execution and reversal logic.
Step 2: Recognize the pattern that supports undo/redo and request queuing
The Command Pattern encapsulates requests as objects, allowing the invoker to queue, log, and manage undo/redo stacks independently from the receiver.
Final Answer:
Option A -> Option A
Quick Check:
Command Pattern enables undo/redo with encapsulated commands [OK]
Hint: Undo/redo needs encapsulated commands, not direct calls [OK]
Common Mistakes:
Thinking direct function calls can support undo/redo cleanly
Confusing Command Pattern with greedy or DP algorithms
2. Given the following Python code using the Builder pattern, what is the output of print(house) after constructing a basic house with the Director?
easy
A. House parts: Walls, Roof
B. House parts: Walls, Roof, Pool
C. House parts: Roof, Walls
D. House parts: Pool
Solution
Step 1: Trace Director.construct_basic_house()
The Director calls build_walls() and build_roof() on the builder, adding "Walls" and "Roof" to the house parts.
Step 2: Check the final house parts list
The house parts list contains ["Walls", "Roof"]. The pool is not added in this method.
Final Answer:
Option A -> Option A
Quick Check:
Only walls and roof are added in basic house construction [OK]
Hint: Basic house adds walls and roof only [OK]
Common Mistakes:
Assuming pool is added by default
Mixing order of parts
3. When a new feature is added by extending a class hierarchy following the Open/Closed Principle, what is the typical sequence of events when the system executes a method call on the new subclass instance?
easy
A. The base class method is always executed first, then the subclass method overrides it afterward
B. The system duplicates the base class code inside the subclass to avoid modifying the base
C. The subclass method is invoked directly due to polymorphism, without modifying base class code
D. Both base and subclass methods execute sequentially because the base class is modified to call the subclass
Solution
Step 1: Understand polymorphic dispatch
Method calls on subclass instances invoke the subclass's overridden method directly.
Step 2: Base class code remains unchanged
The base class is closed for modification; no changes are made to call subclass methods explicitly.
Step 3: Why other options are incorrect
The base class method is always executed first, then the subclass method overrides it afterward incorrectly suggests base method runs first then subclass; Both base and subclass methods execute sequentially because the base class is modified to call the subclass implies base class modification; The system duplicates the base class code inside the subclass to avoid modifying the base suggests code duplication, violating DRY and OCP.
Final Answer:
Option C -> Option C
Quick Check:
Polymorphism enables extension without modifying base code.
Believing base class must be modified to support extension
Thinking code duplication is a valid OCP strategy
4. Imagine a class responsible for both data persistence and data validation. When a change in validation rules occurs, trace the impact on the class and explain what happens step-by-step.
easy
A. Only the validation methods need modification; persistence remains unaffected, so SRP is maintained.
B. Changing validation rules forces modifying the entire class, risking unintended side effects on persistence logic.
C. Validation changes automatically propagate to persistence without code changes due to tight coupling.
D. Persistence logic will break because validation and persistence are tightly integrated and inseparable.
Solution
Step 1: Identify responsibilities
The class handles both validation and persistence, two distinct reasons to change.
Step 2: Trace change impact
Changing validation rules requires modifying validation code inside the class.
Step 3: Side effects
Because persistence logic shares the class, changes risk affecting persistence unintentionally, increasing maintenance risk.
Step 4: SRP violation
This coupling violates SRP, as one reason to change (validation) affects unrelated functionality (persistence).
Final Answer:
Option B -> Option B
Quick Check:
One reason to change should not force changes in unrelated code -> SRP violation.
Hint: One reason to change means one place to modify.
Common Mistakes:
Assuming changes affect only related methods without side effects.
Believing tight coupling is harmless if code is in one class.
Thinking validation and persistence are always linked.
5. You need to ensure that a class has only one instance throughout the application lifecycle, and this instance must be lazily initialized in a thread-safe manner without incurring synchronization overhead on every access. Which design approach best fits this requirement?
easy
A. Use a simple static variable without synchronization and rely on the language's memory model.
B. Use a synchronized method that creates the instance on first call, locking every time.
C. Create the instance eagerly at class loading time to avoid synchronization issues.
D. Implement double-checked locking with a volatile instance variable to minimize synchronization overhead.
Solution
Step 1: Understand the problem constraints
The instance must be lazily initialized and thread-safe, but synchronization overhead should be minimized.
Step 2: Evaluate each approach
Synchronized method locks on every call, causing overhead. Eager initialization is thread-safe but not lazy. Unsynchronized static variable risks multiple instances in multithreaded contexts. Double-checked locking with volatile ensures lazy init, thread safety, and minimal locking.
Final Answer:
Option D -> Option D
Quick Check:
Double-checked locking balances thread safety and performance [OK]