Bird
Raised Fist0

Notification System OOP - Factory Pattern, Channel Abstraction, Template

LLDmediumInterview freq: medium
Choose your preparation mode4 modes available
▶
Class Trace
Step 1/4
NotificationChannel abstract class

NotificationChannel is an abstract class with a synchronized send method and a channelName field.

🎯 Tests whether candidate knows abstract class is chosen over interface because NotificationChannel holds state (channelName) and provides shared synchronized behavior.
public abstract class NotificationChannel {
    protected String channelName;
    public synchronized void send(Notification notification) {
        // Template method with synchronized to ensure thread safety
    }
}
EmailChannel concrete subclass

EmailChannel extends NotificationChannel and overrides the synchronized send method to provide email-specific sending logic.

🎯 Tests realization of abstract class and overriding synchronized method to maintain thread safety in subclass implementations.
public class EmailChannel extends NotificationChannel {
    @Override
    public synchronized void send(Notification notification) {
        // Email sending logic
    }
}
NotificationService composition with NotificationChannelFactory

NotificationService composes NotificationChannelFactory (composition) and exposes a synchronized notifyUser method that uses factory to get channel.

🎯 Tests understanding of composition lifecycle ownership and synchronized method to atomically get channel and send notification.
public class NotificationService {
    private final NotificationChannelFactory factory;
    public NotificationService(NotificationChannelFactory factory) {
        this.factory = factory;
    }
    public synchronized void notifyUser(Notification notification, String channelType) {
        NotificationChannel channel = factory.getChannel(channelType);
        channel.send(notification);
    }
}
NotificationChannelFactory factory method

NotificationChannelFactory provides getChannel method to instantiate concrete NotificationChannel based on channelType string.

🎯 Tests Factory Pattern usage to decouple NotificationService from concrete channel implementations, enforcing OCP.
public class NotificationChannelFactory {
    public NotificationChannel getChannel(String channelType) {
        switch(channelType) {
            case "EMAIL": return new EmailChannel();
            case "SMS": return new SmsChannel();
            case "PUSH": return new PushChannel();
            default: throw new IllegalArgumentException("Unknown channel");
        }
    }
}
📐
Class Diagram
Class Diagram
       +----------------------+
       | <<Abstract>>         |
       | NotificationChannel  |
       +----------------------+
       | - channelName: String|
       +----------------------+
       | + send(Notification) |
       |   [sync]             |
       +----------------------+
               │ extends
       +----------------------+
       | EmailChannel         |
       +----------------------+
       |                      |
       +----------------------+
       | + send(Notification)  |
       |   [sync]             |
       +----------------------+
               │
       +----------------------+
       | NotificationService  |
       +----------------------+
       | - factory: NotificationChannelFactory (◆) |
       +----------------------+
       | + notifyUser(Notification, String channelType) [sync] |
       +----------------------+
               │
       +----------------------+
       | NotificationChannelFactory |
       +----------------------+
       | + getChannel(String): NotificationChannel |
       +----------------------+

This Notification System design uses an abstract class NotificationChannel to hold shared state and synchronized send behavior, which interfaces cannot hold in Java. Concrete subclasses like EmailChannel realize this abstraction and override synchronized methods to maintain thread safety. NotificationService composes NotificationChannelFactory, owning its lifecycle, and exposes a synchronized notifyUser method to atomically get a channel and send notifications, preventing race conditions. The factory method pattern in NotificationChannelFactory decouples channel creation from usage, enforcing the Open-Closed Principle by allowing new channels without modifying existing code. This design balances stateful abstraction, concurrency control with synchronized methods, and extensibility via factory pattern.

Primary Focus

✓ Abstract class vs Interface + Factory Pattern + Composition lifecycle + Template method concurrency

Practice

(1/5)
1. In the Movie Ticket Booking System, the SeatManager class has a private constructor and a static synchronized getInstance() method. Which design pattern is applied here and why is it the correct choice?
easy
A. Prototype pattern, to clone seat states efficiently.
B. Factory pattern, because it controls object creation centrally.
C. Builder pattern, to construct complex seat objects step-by-step.
D. Singleton pattern, to ensure only one instance manages seat states globally.

Solution

  1. Step 1: Identify the pattern from constructor and method signatures

    A private constructor prevents external instantiation, and a static synchronized getInstance() method ensures controlled single instance creation.
  2. Step 2: Reason why Singleton fits seat management

    SeatManager must coordinate seat availability globally to avoid conflicting bookings, so a single shared instance is necessary.
  3. Final Answer:

    Option D -> Option D
  4. Quick Check:

    Private constructor + static synchronized getInstance() -> Singleton pattern [check]
Hint: Private constructor + static getInstance() = Singleton [check]
Common Mistakes:
2. In the Amazon Locker Service design, a LockerManager class has a private constructor and a static synchronized getInstance() method. Which design pattern is applied here and why is it the correct choice?
easy
A. Singleton pattern, to ensure only one instance manages all lockers globally.
B. Factory pattern, because it controls object creation centrally.
C. Builder pattern, to incrementally construct LockerManager with configurations.
D. Prototype pattern, to clone LockerManager instances for scalability.

Solution

  1. Step 1: Identify the pattern from constructor and method

    The private constructor prevents external instantiation, and the static synchronized getInstance() method ensures a single globally accessible instance.
  2. Step 2: Why Singleton fits this use case

    LockerManager must coordinate locker assignments centrally to avoid conflicts, so a single instance is necessary. Factory or Builder do not enforce single instance, Prototype is for cloning, which is not needed here.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Private constructor + synchronized getInstance() -> Singleton pattern [check]
Hint: Private constructor + static getInstance() -> Singleton [check]
Common Mistakes:
3. A class has a private constructor and a static synchronized getInstance() method returning a single instance. Which pattern is applied and why is this the CORRECT use case?
easy
A. Factory Method pattern, because it controls object creation with a static method.
B. Prototype pattern, to clone existing instances instead of creating new ones.
C. Builder pattern, to construct complex objects step-by-step.
D. Singleton pattern, ensuring only one instance exists and is thread-safe.

Solution

  1. Step 1: Identify the pattern from constructor and method

    The private constructor prevents external instantiation, and the static synchronized getInstance() method ensures a single shared instance with thread safety.
  2. Step 2: Eliminate other creational patterns

    Factory Method creates objects but doesn't enforce single instance; Builder constructs complex objects but not singleton; Prototype clones instances, not restricts to one.
  3. Final Answer:

    Option D -> Option D
  4. Quick Check:

    Private constructor + synchronized static getter -> Singleton pattern [check]
Hint: Private constructor + static synchronized getter -> Singleton [check]
Common Mistakes:
4. A Java class has a private constructor and a static synchronized getInstance() method returning the single instance. Which design pattern is applied and why is this the CORRECT use case?
easy
A. Factory pattern, because it controls object creation centrally.
B. Builder pattern, because it constructs complex objects step-by-step.
C. Prototype pattern, because it clones existing instances for reuse.
D. Singleton pattern, because it ensures only one instance with controlled access.

Solution

  1. Step 1: Identify the pattern by constructor and method

    The private constructor prevents external instantiation, and the static synchronized getInstance() method controls access to a single instance, matching Singleton pattern characteristics.
  2. Step 2: Eliminate other patterns

    Factory creates multiple instances, Builder constructs complex objects with multiple steps, and Prototype clones objects; none restrict to a single instance.
  3. Final Answer:

    Option D -> Option D
  4. Quick Check:

    Private constructor + synchronized static getter -> Singleton pattern [check]
Hint: Private constructor + static synchronized getter -> Singleton [check]
Common Mistakes:
5. In a multi-threaded command queue, if the execute() method is not synchronized, which exact sequence of events can cause the same command to be executed twice concurrently?
medium
A. Thread 1 and Thread 2 both call execute() simultaneously and both complete successfully
B. Thread 1 and Thread 2 deadlock waiting for each other's locks
C. Thread 1 finishes execute(), but Thread 2 never sees the updated executed flag due to visibility issues
D. Thread 1 checks if command is not executed, context switch before marking executed; Thread 2 checks same and executes again

Solution

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

    Without synchronization, Thread 1 reads executed=false, then context switches before setting executed=true.
  2. Step 2: Thread 2 reads executed=false and proceeds to execute again

    Both threads execute the command concurrently, causing double execution.
  3. Final Answer:

    Option D -> Option D
  4. Quick Check:

    Unsynchronized check-then-act allows double execution [check]
Hint: Unsynchronized check-then-act causes double execution [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