Bird
Raised Fist0

Notification System OOP - Factory Pattern, Channel Abstraction, Template

LLDmediumInterview freq: medium
Choose your preparation mode4 modes available
๐ŸŽฏ
Design a Notification System OOP with Factory and Template Patterns
40 min ยท medium
medium

You are tasked with designing a notification system that can send messages via multiple channels (Email, SMS, Push). The system must support message templates with placeholders, allow easy addition of new channels without modifying existing code, and centralize channel creation using a Factory pattern.

  • Notification channels include Email, SMS, and Push notifications.
  • Each notification channel has its own way of sending messages but shares a common interface.
  • Message templates contain placeholders to be replaced with actual values at runtime.
  • The system must allow adding new notification channels without modifying existing classes (Open/Closed Principle).
  • Channel creation must be centralized using a Factory pattern to encapsulate instantiation logic.
๐Ÿ“‹
Guided Steps
1/5
Step 1
Define Notification Channel Abstraction
Design an abstraction for notification channels that enforces a send method. Decide whether to use an interface or abstract class and justify your choice.
๐Ÿ’ก Hint
Consider if the abstraction needs to hold shared instance state or provide default behavior. Remember, interfaces in Java cannot hold instance fields.
Use an abstract class NotificationChannel with protected String channelName field and abstract sendNotification(Map<String,String> data) method - abstract class chosen because it holds shared instance state (channelName) which interfaces cannot hold in Java.
Mid: Names Template Method pattern for sendNotification abstraction. Senior: Explains why abstract class is needed due to shared instance state channelName; interface cannot hold fields in Java.
โš ๏ธCommon Wrong Answer
Using interface NotificationChannel with channelName field leads to compilation error because interfaces cannot have instance fields in Java.
Step 2
Implement Message Template with Placeholder Replacement
Design a MessageTemplate class that supports placeholders and can generate final messages by replacing placeholders with actual values.
๐Ÿ’ก Hint
Use a method that takes a Map<String,String> for placeholder values and returns the processed message string. Consider immutability and thread-safety.
MessageTemplate class with final String templateText field and public String render(Map<String,String> placeholders) method; templateText is immutable to ensure thread-safety without synchronization.
Mid: Recognizes Template pattern for message rendering. Senior: Explains immutability of templateText avoids synchronization issues in concurrent access.
โš ๏ธCommon Wrong Answer
Mutable templateText with unsynchronized render method causes race conditions when accessed by multiple threads.
Step 3
Design NotificationChannelFactory for Channel Creation
Create a factory class to instantiate notification channels based on a channel type enum. Explain how this supports Open/Closed Principle.
๐Ÿ’ก Hint
Use an enum ChannelType and a factory method that returns NotificationChannel instances. Avoid exposing constructors directly.
NotificationChannelFactory with public static NotificationChannel createChannel(ChannelType type) method; ChannelType is enum to ensure compile-time safety and exhaustive switch.
Mid: Names Factory pattern for channel creation. Senior: Explains enum usage prevents runtime typo bugs and supports exhaustive switch, enhancing maintainability.
โš ๏ธCommon Wrong Answer
Using String for channel type leads to runtime errors due to typos and no compile-time checks.
Step 4
Implement Concrete Notification Channels Using Template Method
Implement concrete classes EmailChannel, SMSChannel, and PushChannel extending NotificationChannel. Use Template Method pattern to define sendNotification steps.
๐Ÿ’ก Hint
Define final sendNotification method in abstract class calling abstract methods implemented by subclasses for channel-specific sending logic.
NotificationChannel defines final sendNotification(Map<String,String>) method invoking abstract sendMessage(String message) implemented by subclasses; ensures fixed algorithm structure with customizable steps.
Mid: Identifies Template Method pattern usage. Senior: Explains final method prevents subclasses from altering algorithm structure, ensuring consistent notification flow.
โš ๏ธCommon Wrong Answer
Allowing subclasses to override sendNotification breaks algorithm consistency and may cause inconsistent notification behavior.
Step 5
Ensure Thread-Safe Notification Sending
Design the sendNotification method and related state to be thread-safe for concurrent notification sending.
๐Ÿ’ก Hint
Consider if any mutable shared state exists. Use synchronized or immutable objects as needed.
sendNotification is stateless and uses immutable MessageTemplate; no synchronization needed. If mutable state added later, use synchronized on sendNotification to prevent race conditions.
Mid: Recognizes stateless design avoids concurrency issues. Senior: Explains that if mutable state is introduced, synchronized on sendNotification ensures atomic execution preventing interleaving bugs.
โš ๏ธCommon Wrong Answer
Ignoring concurrency leads to race conditions when multiple threads send notifications simultaneously, causing corrupted message content or lost notifications.
๐Ÿ”Œ
API Design Challenge
Design an API that allows clients to send notifications via different channels using message templates with placeholders. The API should enable adding new channels without changing existing client code and centralize channel creation.
  • Use an enum to represent channel types for compile-time safety.
  • Implement a factory class with a static method to create channel instances based on the enum.
  • Design message templates with a render method that replaces placeholders with actual values.
๐Ÿ“Š
Scoring Rubric
โ–ผ Show
Must Have - 60%
  • abstract class NotificationChannel with shared instance state channelName
  • MessageTemplate with immutable templateText and render method
  • enum ChannelType for channel identification
  • NotificationChannelFactory with static createChannel method
  • Template Method pattern with final sendNotification and abstract sendMessage
Good to Have - 30%
  • Thread-safe design via immutability and stateless methods
  • Open/Closed Principle adherence by subclassing and factory extension
  • Use of final methods to enforce algorithm structure
  • Avoidance of String for channel types to prevent runtime bugs
Bonus - 10%
  • Explanation of concurrency scenarios with exact thread interleaving
  • Use of synchronized if mutable state introduced in future
โœ…
Model Answer
โ–ผ Reveal

The notification system is designed using Java's abstract classes and enums to enforce type safety and extensibility. NotificationChannel is an abstract class holding shared state and defining a final sendNotification method following the Template Method pattern. Concrete channels like EmailChannel, SMSChannel, and PushChannel extend this class and implement channel-specific sending logic. MessageTemplate is immutable and thread-safe, supporting placeholder replacement via a render method. NotificationChannelFactory centralizes channel creation using a static method and an enum ChannelType, supporting the Open/Closed Principle by allowing new channels to be added without modifying existing code.

  • Use abstract class NotificationChannel to hold shared state channelName and enforce Template Method pattern with final sendNotification
  • Implement MessageTemplate as immutable to ensure thread-safety without synchronization
  • Use enum ChannelType to avoid runtime errors and enable exhaustive switch in factory
  • Centralize channel creation in NotificationChannelFactory with static method to adhere to Open/Closed Principle
  • Make sendNotification stateless and rely on immutable MessageTemplate to ensure thread-safe notification sending
  • abstract class NotificationChannel with protected channelName field and abstract sendMessage(String message) method
  • MessageTemplate class with immutable final templateText and render(Map<String,String>) method for placeholder replacement
  • enum ChannelType for compile-time safe channel identification
  • NotificationChannelFactory with static createChannel(ChannelType) method implementing Factory pattern
  • Template Method pattern in NotificationChannel: final sendNotification(Map<String,String>) calls abstract sendMessage(String)
  • Thread-safe design by making MessageTemplate immutable and sendNotification stateless
  • Avoid String usage for channel types to prevent runtime typo bugs
  • Use final methods in abstract class to enforce algorithm structure
  • Open/Closed Principle enforced by adding new channels via subclassing and factory extension without modifying existing code
  • Use of Java-specific constructs: abstract class for shared state, enum for type safety, final methods for template pattern, and immutability for concurrency

Practice

(1/5)
1. In the Library Management System, the ReservationManager class has a private constructor and a static synchronized getInstance() method. Which design pattern is applied here and why is this the correct use case?
easy
A. Prototype pattern, to clone ReservationManager instances efficiently.
B. Factory pattern, because it controls object creation centrally.
C. Builder pattern, to construct ReservationManager with complex parameters.
D. Singleton pattern, to ensure only one ReservationManager instance manages all reservations.

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 instance is created and accessed globally.
  2. Step 2: Why Singleton fits ReservationManager

    ReservationManager must coordinate all reservations centrally to avoid conflicts; multiple instances would cause inconsistent state.
  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 designing the User and Message classes for the messaging system, should User be an abstract class or an interface, and why?
easy
A. User should be an interface to allow multiple inheritance of user behaviors.
B. User should be an interface because it only defines behavior without state.
C. User should be an abstract class because it holds common user state like userId and username.
D. User should be an abstract class to enforce implementation of all methods in subclasses.

Solution

  1. Step 1: Analyze if User has state or just behavior

    User holds common state such as userId and username, which requires fields and possibly default implementations.
  2. Step 2: Determine if abstract class or interface fits state and behavior

    Abstract class supports shared state and partial implementation, while interface cannot hold instance state.
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    User has common state -> abstract class fits better than interface [check]
Hint: Stateful base class -> abstract class, stateless -> interface [check]
Common Mistakes:
3. In designing the ESPNcricinfo system, should the Ball entity be modeled as an abstract class or an interface? Consider that Ball has state like ballNumber and behavior like calculateRuns(). Which is the best choice and why?
easy
A. Abstract class, because Ball has shared state and behavior that subclasses inherit.
B. Interface, because Ball only defines behavior and no state should be stored.
C. Interface, because Java interfaces can now have default methods with state variables.
D. Abstract class, because it allows multiple inheritance of Ball features.

Solution

  1. Step 1: Analyze Ball's state and behavior

    Ball has state (ballNumber) and behavior (calculateRuns), so it needs to store data and provide common logic.
  2. Step 2: Abstract class vs interface for state

    Interfaces cannot hold instance state; abstract classes can hold state and provide shared implementation.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Stateful entity with behavior -> abstract class [check]
Hint: Stateful with behavior -> abstract class, stateless behavior -> interface [check]
Common Mistakes:
4. You want to add a WeekendFeeStrategy to calculate plugin usage fees on weekends without modifying existing plugin classes. Which SOLID principle guides the minimum change, and what is the correct approach?
medium
A. Open-Closed Principle: create a new WeekendFeeStrategy class implementing FeeStrategy; existing classes remain unchanged.
B. Dependency Inversion Principle: inject WeekendFeeStrategy into Plugin constructor, modifying Plugin to accept it.
C. Single Responsibility Principle: modify Plugin class to add weekend fee logic.
D. Liskov Substitution Principle: subclass Plugin to override fee calculation for weekends.

Solution

  1. Step 1: Identify the SOLID principle for adding new behavior without modifying existing code

    The Open-Closed Principle (OCP) states software entities should be open for extension but closed for modification.
  2. Step 2: Apply OCP by creating a new WeekendFeeStrategy implementing FeeStrategy interface

    This allows adding weekend fee logic without changing existing plugin or fee classes.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    OCP: extend with new strategy class, no existing class changes [check]
Hint: OCP: add new class, don't modify existing ones [check]
Common Mistakes:
5. To add a new WeekendFeeStrategy pricing rule in the Workflow Engine's fee calculation, which minimal change respects SOLID principles?
medium
A. Modify the existing FeeCalculator class to add weekend logic inside its calculate() method.
B. Create a new WeekendFeeStrategy class implementing FeeStrategy interface and inject it without changing FeeCalculator.
C. Add a boolean flag isWeekend to FeeCalculator and branch logic accordingly.
D. Combine all fee strategies into one large class with if-else for each pricing rule.

Solution

  1. Step 1: Identify SOLID principle involved

    Open-Closed Principle (OCP) requires classes to be open for extension but closed for modification.
  2. Step 2: Apply OCP to fee strategy

    Adding a new WeekendFeeStrategy class extends behavior without modifying existing FeeCalculator, respecting OCP and Dependency Inversion Principle.
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    New strategy class + no FeeCalculator change -> OCP respected [check]
Hint: OCP: extend with new class, don't modify existing [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