Practice
import threading
class Observer:
def __init__(self, name):
self.name = name
def update(self, event_type, message):
print(f"{self.name} received {event_type}: {message}")
class Subject:
def __init__(self):
self.lock = threading.Lock()
self.observers = {}
def subscribe(self, observer, event_type):
with self.lock:
if event_type not in self.observers:
self.observers[event_type] = set()
self.observers[event_type].add(observer)
def unsubscribe(self, observer, event_type):
with self.lock:
if event_type in self.observers and observer in self.observers[event_type]:
self.observers[event_type].remove(observer)
if not self.observers[event_type]:
del self.observers[event_type]
def notify(self, event_type, message):
with self.lock:
observers_snapshot = list(self.observers.get(event_type, []))
for observer in observers_snapshot:
observer.update(event_type, message)
subject = Subject()
obs1 = Observer('Obs1')
obs2 = Observer('Obs2')
subject.subscribe(obs1, 'eventA')
subject.subscribe(obs2, 'eventB')
subject.notify('eventA', 'Hello A')
subject.notify('eventB', 'Hello B')
subject.unsubscribe(obs1, 'eventA')
subject.notify('eventA', 'Hello again A')
What is printed?Solution
Step 1: Trace subscriptions and notifications
Obs1 subscribes to 'eventA', Obs2 to 'eventB'. First notify('eventA') calls Obs1.update, printing "Obs1 received eventA: Hello A". Then notify('eventB') calls Obs2.update, printing "Obs2 received eventB: Hello B".Step 2: Trace unsubscribe and final notification
Obs1 unsubscribes from 'eventA'. The final notify('eventA') finds no observers, so no output for Obs1. Therefore, the last notification does not print anything.Final Answer:
Option D -> Option DQuick Check:
Unsubscribed observers do not receive notifications [OK]
- Assuming unsubscribed observers still get notified
k decorators on a core object using the Decorator Pattern with dynamic behavior injection?Solution
Step 1: Identify call chain length
Each decorator's cost() calls the wrapped object's cost(), forming a chain of length k.Step 2: Calculate total calls
Cost computation requires traversing all k decorators once, so time complexity is O(k).Final Answer:
Option B -> Option BQuick Check:
Each decorator adds constant work, total linear in k [OK]
- Assuming O(1) because cost is a simple addition
- Mistaking recursive calls as quadratic
Solution
Step 1: Identify stack initialization issue
Stack is initialized with children in original order, not reversed, causing traversal order reversal.Step 2: Confirm impact on traversal order
Without reversing, popping from stack yields children in reverse order, breaking expected traversal.Final Answer:
Option A -> Option AQuick Check:
Reversing children on stack initialization fixes traversal order [OK]
- Forgetting to reverse children on stack push
- Misplacing hasNext() check
Solution
Step 1: Understand SRP effect
SRP encourages splitting responsibilities, which can increase the number of classes.Step 2: Trade-off analysis
More classes improve modularity but can make the system harder to navigate and understand due to fragmentation.Step 3: Evaluate other options
It always reduces code complexity and never increases the number of classes. is false because SRP can increase class count; It forces all classes to be tightly coupled to maintain cohesion. is false because SRP reduces coupling; It eliminates the need for interfaces or abstractions in the system. is false because SRP does not remove the need for abstractions.Final Answer:
Option C -> Option CQuick Check:
More classes can complicate system navigation -> trade-off of SRP.
- Believing SRP always simplifies codebase size.
- Confusing coupling with cohesion effects.
- Assuming SRP removes need for design patterns.
Solution
Step 1: Understand dynamic behavior change
To change behaviors at runtime, the design must support swapping behavior implementations easily.Step 2: Analyze options
Ensure that composed behavior objects are immutable to prevent side effects. is not practical; immutability limits dynamic changes. Avoid using interfaces and rely on concrete classes to reduce complexity. increases coupling and reduces flexibility. Use deep inheritance hierarchies to capture all behavior variations upfront. contradicts favoring composition and leads to rigid designs.Step 3: Confirm best practice
Design behavior interfaces and use delegation so behaviors can be swapped without modifying the main object. promotes defining behavior interfaces and delegating calls, enabling runtime swapping without modifying main objects.Final Answer:
Option D -> Option DQuick Check:
Delegation with interfaces is key to flexible, dynamic composition.
- Thinking immutability aids dynamic behavior changes
- Relying on inheritance for runtime flexibility
