Observer Pattern: Interview Questions and Variations
Answer: Use a tiered notification strategy. First, group observers by interest (e.g., observers interested in AAPL vs GOOGL). Second, implement a notification manager that filters events so only relevant observers are notified. Third, for high-frequency updates, batch notifications using a time window (e.g., notify at most every 100ms). Fourth, consider async notifications using a thread pool to prevent slow observers from blocking others, but note this introduces complexity around notification order and state consistency.
Answer: Synchronous (default) means observers are notified in the same thread, making debugging easier and ensuring immediate consistency. Use this when observer operations are fast and order matters. Asynchronous means observers are notified via separate threads or event queue, preventing slow observers from blocking subject operations. Use this for IO-bound observers (sending emails, logging to external systems), but be aware that subject state may change before observer reads it, requiring snapshot passing or state versioning.
Pull Model: Subject notifies observers with minimal or no data. Observers query subject for details they need. Advantage: Observers get only the data they care about. Disadvantage: Multiple queries can be inefficient, and subject state might change between notification and query.
Push Model: Subject sends all relevant data with notification. Advantage: More efficient, observers receive consistent snapshot. Disadvantage: Subject must know what data observers need, reducing flexibility. Sends unnecessary data to observers that do not need it all.
Hybrid Model: Subject sends essential data but allows observers to pull additional details. Example: notify with event type and entity ID, let observers load full entity if needed. This balances efficiency and flexibility.
First, decide on observer registration lifetime. Should observers auto-detach when garbage collected, or require explicit cleanup? Explicit is safer and clearer for interviews.
Second, handle observer exceptions gracefully. Wrap each observer notification in try-catch, log the error, and continue notifying other observers. Do not let one faulty observer break the notification chain.
Third, consider notification during iteration. If an observer detaches itself or adds new observers during notification, you will modify the collection while iterating. Copy the observer list before iterating: for observer in observers.copy().
Fourth, define clear observer interface methods. Instead of generic update(), use specific methods like onPriceChanged(symbol, newPrice). This makes the contract explicit and type-safe.
class Subject:observers = []attach(observer): observers.append(observer)detach(observer): observers.remove(observer)notifyObservers(event):for obs in observers.copy():try: obs.update(event)catch Exception: log errorinterface Observer:update(event): voidConditional Updates: Not all observers need every notification. Add an interest filter: observers register with predicates (e.g., "notify only if price change exceeds 5%"). Subject evaluates predicates before notifying each observer.
Priority Observers: Some observers must be notified before others (e.g., billing before logging). Maintain observers in a priority queue or sorted list. Sort by priority before iteration in notifyObservers().
Transaction Boundaries: In systems with transactions, defer notifications until transaction commits. Accumulate events during transaction, notify observers only on successful commit. On rollback, discard events. This ensures observers never see invalid intermediate states.