Bird
Raised Fist0

Observer Pattern - Event-Driven Design and Decoupling Publisher from Subscriber

LLDeasyInterview freq: high
Choose your preparation mode4 modes available
๐ŸŽฏ
Observer Pattern - Event-Driven Design and Decoupling Publisher from Subscriber
40 min ยท medium
medium

Design a Java-based Observer pattern system where multiple Subscribers can register to receive events from a Publisher. The system must support dynamic subscription and unsubscription at runtime, asynchronous event dispatching, and thread-safe notification to avoid race conditions and inconsistent state.

  • Publisher maintains a list of Subscribers interested in events.
  • Subscribers can subscribe or unsubscribe at any time, including during event notification.
  • Event notification must be asynchronous to avoid blocking the Publisher.
  • Thread safety is critical: concurrent subscription, unsubscription, and event dispatch can occur.
  • Subscribers must receive events in the order they were published.
  • The system should allow extension for different event types without modifying existing classes.
๐Ÿ“‹
Guided Steps
1/5
Step 1
Define the Subscriber interface
Design the Subscriber interface that declares the method to receive events. Consider how to represent the event data and ensure the interface can be implemented by various subscriber types.
๐Ÿ’ก Hint
Use a generic interface with a method like public void onEvent(Event e). Interfaces in Java cannot hold instance state but define behavior contracts.
Use a generic interface Subscriber<T> with method public void onEvent(T event) - interface chosen because it defines behavior without holding state, enabling multiple inheritance and loose coupling.
Mid: Names Observer pattern and interface usage. Senior: Explains interface cannot hold instance fields in Java, enabling multiple inheritance and decoupling.
โš ๏ธCommon Wrong Answer
Using an abstract class for Subscriber unnecessarily restricts multiple inheritance and forces state management even when not needed.
Step 2
Design the Publisher class with thread-safe subscription management
Implement the Publisher class that maintains Subscribers and allows thread-safe subscribe/unsubscribe operations. Consider the data structure and synchronization mechanisms.
๐Ÿ’ก Hint
Use CopyOnWriteArrayList or synchronize subscription methods to avoid ConcurrentModificationException during iteration.
Use private final CopyOnWriteArrayList<Subscriber<T>> subscribers to hold subscribers - CopyOnWriteArrayList provides thread-safe iteration and modification without explicit synchronization.
Mid: Describes thread-safe collection usage for subscriber list. Senior: Explains how CopyOnWriteArrayList avoids race conditions by copying on write, preventing ConcurrentModificationException during concurrent iteration and modification.
โš ๏ธCommon Wrong Answer
Using ArrayList with synchronized methods leads to race conditions during iteration causing ConcurrentModificationException.
Step 3
Implement asynchronous event notification
Design the event dispatch method in Publisher to notify all Subscribers asynchronously without blocking the Publisher thread.
๐Ÿ’ก Hint
Use ExecutorService with a fixed thread pool or a single-thread executor to submit notification tasks.
Use private final ExecutorService executor = Executors.newSingleThreadExecutor() to dispatch events asynchronously - ensures event order and non-blocking Publisher thread.
Mid: Names asynchronous dispatch using ExecutorService. Senior: Explains single-thread executor preserves event order, and submitting Runnable tasks avoids blocking Publisher thread.
โš ๏ธCommon Wrong Answer
Calling Subscriber.onEvent() directly in Publisher thread blocks event publishing and risks deadlocks if Subscriber is slow.
Step 4
Handle dynamic subscription changes during event notification
Ensure that Subscribers can subscribe or unsubscribe even while events are being dispatched without causing ConcurrentModificationException or inconsistent notifications.
๐Ÿ’ก Hint
CopyOnWriteArrayList allows safe concurrent modification during iteration; no additional synchronization needed for iteration.
Rely on CopyOnWriteArrayList semantics where iteration uses a snapshot of the list, allowing concurrent subscribe/unsubscribe without synchronization or exceptions.
Mid: Recognizes CopyOnWriteArrayList solves concurrent modification. Senior: Details how iteration snapshot semantics prevent race conditions and ensure consistent event delivery.
โš ๏ธCommon Wrong Answer
Using synchronized List and iterating without snapshot causes ConcurrentModificationException when subscribers change during notification.
Step 5
Design extensibility for multiple event types
Allow the system to support different event types without modifying existing Publisher or Subscriber classes.
๐Ÿ’ก Hint
Use Java generics and inheritance for event types; Publisher and Subscriber should be generic over event type T.
Make Publisher<T> and Subscriber<T> generic interfaces/classes - enables open-closed principle by allowing new event types without modifying existing code.
Mid: Names use of generics for type safety and extensibility. Senior: Explains how generics enforce compile-time type checks and prevent casting errors.
โš ๏ธCommon Wrong Answer
Using Object for event type leads to unsafe casting and runtime ClassCastException.
๐Ÿ”Œ
API Design Challenge
Design a Java API for an event-driven Observer pattern system that supports dynamic subscription, asynchronous event dispatch, and thread safety. The API should allow multiple event types and ensure subscribers receive events in order.
  • Use Java generics to support multiple event types and enforce type safety.
  • Use thread-safe collections like CopyOnWriteArrayList to manage subscribers safely during concurrent modifications.
  • Use ExecutorService with a single-thread executor to dispatch events asynchronously while preserving event order.
๐Ÿ“Š
Scoring Rubric
โ–ผ Show
Must Have - 60%
  • Subscriber interface with generic method onEvent(T event)
  • Publisher class with CopyOnWriteArrayList for subscribers
  • Asynchronous event dispatch using ExecutorService
  • Thread-safe subscribe and unsubscribe methods
  • Generics used for event type safety and extensibility
Good to Have - 30%
  • Graceful shutdown of ExecutorService in Publisher
  • Use of final fields for safe publication
  • Explanation of CopyOnWriteArrayList snapshot semantics
  • Preservation of event order via single-thread executor
Bonus - 10%
  • Support for multiple event types with inheritance
  • Detailed concurrency s
โœ…
Model Answer
โ–ผ Reveal

    • Subscriber<T> interface defines public void onEvent(T event) - interface chosen because it cannot hold state but defines behavior contract.
    • Publisher<T> uses private final CopyOnWriteArrayList<Subscriber<T>> subscribers for thread-safe subscription management without explicit synchronization.
    • ExecutorService with single-thread executor used for asynchronous event dispatch preserving event order and non-blocking Publisher thread.
    • CopyOnWriteArrayList iteration snapshot semantics allow safe concurrent subscribe/unsubscribe during event notification without ConcurrentModificationException.
    • Publisher<T> and Subscriber<T> are generic to support multiple event types, enforcing compile-time type safety and extensibility (Open-Closed Principle).
    • Subscription methods subscribe(Subscriber<T>) and unsubscribe(Subscriber<T>) are public and thread-safe due to CopyOnWriteArrayList internal synchronization.
    • Event dispatch method publishEvent(T event) submits notification tasks to ExecutorService, ensuring asynchronous delivery.
    • Shutdown method in Publisher to gracefully terminate ExecutorService, preventing resource leaks.
    • Avoid synchronized blocks for subscriber list to prevent deadlocks and improve concurrency.
    • Use of final fields for subscribers list and executor to ensure safe publication and visibility guarantees (Java Memory Model).

    Practice

    (1/5)
    1. In a vehicle rental system, should Vehicle be designed as an abstract class or an interface if it needs to hold common state like licensePlate and rentalStatus? Why?
    easy
    A. Abstract class, because it forces all subclasses to implement all methods explicitly.
    B. Interface, because it allows multiple inheritance and enforces method signatures without state.
    C. Interface, because Java interfaces can now have default methods and fields for state.
    D. Abstract class, because it can hold shared state and provide default behavior for subclasses.

    Solution

    1. Step 1: Analyze state requirements

      Vehicle needs to hold common mutable state like licensePlate and rentalStatus, which interfaces cannot hold as fields.
    2. Step 2: Choose correct OOP construct

      Abstract classes can have instance variables and default method implementations, making them suitable for shared state. Interfaces cannot hold instance state, only constants.
    3. Final Answer:

      Option D -> Option D
    4. Quick Check:

      Shared mutable state -> abstract class, not interface [check]
    Hint: Shared state needs abstract class, interfaces only define behavior [check]
    Common Mistakes:
    2. A class SecureImageProxy controls access to RealImage by checking user permissions before loading the image data lazily. Which design pattern is this an example of, and why is it the correct choice here?
    easy
    A. Proxy pattern, because it controls access and delays expensive object creation until needed.
    B. Decorator pattern, because it adds responsibilities dynamically without changing the original object.
    C. Adapter pattern, because it converts the interface of RealImage to a different one.
    D. Facade pattern, because it provides a simplified interface to a complex subsystem.

    Solution

    1. Step 1: Identify the role of SecureImageProxy

      This class controls access to RealImage and delays loading until necessary, matching Proxy's intent.
    2. Step 2: Eliminate other patterns

      Decorator adds behavior without access control; Adapter changes interface; Facade simplifies interface but doesn't control access or lazy load.
    3. Final Answer:

      Option A -> Option A
    4. Quick Check:

      Proxy controls access and lazy loads expensive objects [check]
    Hint: Proxy controls access and lazy loads objects [check]
    Common Mistakes:
    3. In the Food Ordering System, the KitchenQueue class has a parkOrder() method that checks if a slot is available before assigning an order. Without synchronization on parkOrder(), which exact thread interleaving causes double-booking of the same slot?
    medium
    A. Thread 1 and Thread 2 both call parkOrder() simultaneously and the JVM throws an exception.
    B. Thread 1 checks slot availability -> true, context switch before assignment; Thread 2 checks availability -> true, both assign same slot.
    C. Thread 1 assigns slot, then Thread 2 waits for Thread 1 to finish before assigning next slot.
    D. Thread 1 and Thread 2 both wait on a lock, causing deadlock and no assignment.

    Solution

    1. Step 1: Identify the check-then-act race condition

      Without synchronization, Thread 1 reads slot as available but is preempted before assignment; Thread 2 also reads available and proceeds.
    2. Step 2: Both threads assign the same slot leading to double-booking

      Because the availability check and assignment are not atomic, both threads assign the same slot concurrently.
    3. Final Answer:

      Option B -> Option B
    4. Quick Check:

      Check-then-act gap without sync -> double assignment [check]
    Hint: Check availability -> context switch -> both assign same slot [check]
    Common Mistakes:
    4. To add ElectricCar needing ChargingSpot support, what is the minimum change to the current design without modifying existing classes?
    hard
    A. Add ELECTRIC_CAR to VehicleType, CHARGING to SpotType, create ElectricCar extending Vehicle, and update SpotCompatibility.COMPATIBILITY_MAP accordingly.
    B. Modify Vehicle and ParkingSpot classes to include electric-specific fields and logic.
    C. Create a new ElectricVehicleManager class handling electric cars and charging spots, modifying parking logic.
    D. Add conditional logic in ParkingLot to handle electric cars and charging spots explicitly.

    Solution

    1. Step 1: Identify extensibility requirements

      Adding ElectricCar and ChargingSpot support must avoid modifying existing classes to comply with OCP and maintain code modularity.
    2. Step 2: Evaluate options for minimal invasive change

      Add ELECTRIC_CAR to VehicleType, CHARGING to SpotType, create ElectricCar extending Vehicle, and update SpotCompatibility.COMPATIBILITY_MAP accordingly. extends enums and adds new classes, updating compatibility map without changing existing classes. Options B, C, and D modify existing classes or add god-class logic, violating OCP and increasing complexity.
    3. Step 3: Confirm Java-specific implementation details

      Updating enums and compatibility map is idiomatic in Java for extensibility without class explosion.
    4. Final Answer:

      Option A -> Option A
    5. Quick Check:

      Extend enums + new class + compatibility map update -> minimal change [check]
    Hint: Extend enums + new class + compatibility map -> minimal invasive change [check]
    Common Mistakes:
    5. You need to add support for ElectricCar and ChargingSpot in the parking system without modifying existing classes. What is the minimum change to the current design?
    hard
    A. Modify Vehicle and Spot classes to include electric-specific fields and logic.
    B. Add ELECTRIC_CAR to VehicleType enum, CHARGING to SpotType enum, create ElectricCar subclass, and update compatibility map externally.
    C. Create a new ElectricVehicle interface and make ElectricCar implement it, modifying existing classes to check for this interface.
    D. Add a boolean isElectric flag to Vehicle and Spot classes and handle logic with conditionals.

    Solution

    1. Step 1: Identify extensibility without modifying existing classes

      Enums can be extended with new constants, and new subclasses can be added without changing existing code.
    2. Step 2: Use external compatibility map update

      Updating the compatibility map externally avoids modifying existing classes, preserving OCP and avoiding class explosion.
    3. Step 3: Avoid flags or interface checks that require existing class changes

      Flags or interface checks require modifying existing classes, violating OCP.
    4. Final Answer:

      Option B -> Option B
    5. Quick Check:

      Extend enums + new subclass + external map update -> minimal change [check]
    Hint: Extend enums + subclass + external map -> minimal change [check]
    Common Mistakes:
    Recommended

    Ready to prove it?

    Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

    10 questions~5 mineasy hard