Final understanding of runtime polymorphism via overriding.
Shape
+draw()
Circle
+draw()
Rectangle
+draw()
Circle ▷ ShapeRectangle ▷ Shape
Key Takeaways
✓ Runtime polymorphism allows method calls to invoke the correct subclass implementation dynamically.
This dynamic dispatch is not obvious from reading code alone but is clear when watching method calls resolve at runtime.
✓ Overriding replaces base class methods in subclasses, enabling flexible behavior customization.
Seeing the overridden methods highlighted helps understand how subclasses change behavior.
✓ The base class method is called only if no subclass override exists, showing fallback behavior.
This clarifies that polymorphism does not always mean subclass methods are called; base methods remain accessible.
Practice
(1/5)
1. Trace the sequence of events when a client calls a method on a subclass instance that violates the Liskov Substitution Principle by strengthening a postcondition. What happens step-by-step?
easy
A. The client receives a result that meets the superclass contract, so no issues arise.
B. The subclass method returns a stricter result than expected, potentially causing client failures.
C. The client silently ignores the stricter postcondition, so behavior is unaffected.
D. The subclass method throws an exception due to the strengthened postcondition.
Solution
Step 1: Recall LSP postcondition rule
Subclasses must not strengthen postconditions; they can only maintain or weaken them.
Step 2: Trace client call
The client expects results conforming to the superclass contract. If subclass returns stricter results, some clients expecting broader results may fail.
Step 3: Analyze options
The subclass method returns a stricter result than expected, potentially causing client failures. correctly identifies potential client failures due to stricter postconditions. The client receives a result that meets the superclass contract, so no issues arise. is false because stricter postconditions can break clients. The client silently ignores the stricter postcondition, so behavior is unaffected. is incorrect; clients cannot ignore contract violations silently. The subclass method throws an exception due to the strengthened postcondition. is not guaranteed; exceptions are not implied by postcondition strengthening.
Hint: Strengthening postconditions breaks client assumptions and causes failures.
Common Mistakes:
Assuming stricter postconditions are safe
Believing clients ignore contract violations
Confusing exceptions with contract violations
2. 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]
3. What is a common trade-off or limitation when applying Dependency Inversion Principle (DIP) with heavy use of Dependency Injection frameworks?
medium
A. It always improves runtime performance by reducing object creation overhead.
B. It eliminates the need for interfaces or abstractions entirely.
C. It can increase complexity and reduce code readability due to indirect dependencies and configuration.
D. It guarantees compile-time safety without any runtime errors.
Solution
Step 1: Understand DIP and DI trade-offs
While DIP and DI improve modularity, heavy use of DI frameworks can add complexity and obscure dependencies.
Step 2: Analyze options
It can increase complexity and reduce code readability due to indirect dependencies and configuration. correctly identifies increased complexity and reduced readability as a trade-off. It always improves runtime performance by reducing object creation overhead. is false; DI can add runtime overhead. It eliminates the need for interfaces or abstractions entirely. is wrong; DIP requires abstractions. It guarantees compile-time safety without any runtime errors. is incorrect; runtime errors can still occur due to misconfiguration.
Final Answer:
Option C -> Option C
Quick Check:
DI frameworks improve flexibility but can complicate understanding and debugging.
Hint: DI improves modularity but can complicate code and configs.
Common Mistakes:
Assuming DI always improves performance
Believing DIP removes need for interfaces
Thinking DI guarantees no runtime errors
4. Identify the bug in the following singleton implementation that aims to use lazy initialization with double-checked locking in Java-like pseudocode:
class Singleton {
private static Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized(Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
What is the subtle bug that can cause thread-safety issues?
medium
A. The synchronized block is too large, causing unnecessary locking overhead.
B. The instance variable is not declared volatile, risking instruction reordering.
C. The first null check outside synchronized block is redundant and should be removed.
D. The constructor is not private, allowing multiple instances externally.
Hint: Volatile prevents instruction reordering in double-checked locking [OK]
Common Mistakes:
Ignoring volatile keyword necessity
Thinking synchronized block size is the bug
Assuming constructor privacy is the main issue here
5. Suppose you want to extend the observer pattern to allow observers to receive multiple notifications for the same event type without unsubscribing (i.e., observers can be registered multiple times for the same event). Which modification to the thread-safe observer pattern implementation below correctly supports this requirement without breaking thread safety or notification correctness?
Options:
A) Change the observers data structure from a set to a list per event type and allow duplicates.
B) Keep using a set but add a counter for each observer to track multiple subscriptions.
C) Use a dictionary mapping observers to their subscription counts per event type.
D) Use a queue per event type to enqueue notifications and process them asynchronously.
hard
A. Keep using a set but add a counter for each observer to track multiple subscriptions.
B. Change the observers data structure from a set to a list per event type and allow duplicates.
C. Use a dictionary mapping observers to their subscription counts per event type.
D. Use a queue per event type to enqueue notifications and process them asynchronously.
Solution
Step 1: Understand the requirement
Observers can subscribe multiple times to the same event type and should receive multiple notifications accordingly.
Step 2: Evaluate data structure changes
Using a set alone disallows duplicates. A list allows duplicates but is not thread-safe and inefficient for removals. A dictionary mapping observers to counts tracks multiple subscriptions safely.
Step 3: Choose the best approach
Adding a counter per observer in the set (Keep using a set but add a counter for each observer to track multiple subscriptions.) or using a dictionary (Use a dictionary mapping observers to their subscription counts per event type.) can work, but adding a counter per observer in the set is simpler and keeps thread safety with minimal changes.
Final Answer:
Option A -> Option A
Quick Check:
Counting subscriptions per observer preserves multiple notifications safely [OK]
Hint: Track subscription counts to allow multiple notifications [OK]
Common Mistakes:
Using list causes concurrency issues and inefficient removals