Bird
Raised Fist0

Decorator Pattern - Adding Behavior to Objects Without Inheritance

LLDeasyInterview freq: common
Choose your preparation mode4 modes available
๐ŸŽฏ
Decorator Pattern - Adding Behavior to Objects Without Inheritance
40 min ยท medium
medium

Design a notification system where base notifications can be dynamically extended with additional behaviors such as encryption, compression, and logging without modifying existing classes or using inheritance chains. The system must support layering multiple decorators and ensure thread-safe sending of notifications.

  • Base Notification interface defines send() method.
  • Decorators add behaviors like encryption, compression, and logging.
  • Multiple decorators can wrap a single notification object in any order.
  • Decorator classes must hold state related to their behavior (e.g., encryption key).
  • send() method must be thread-safe to prevent concurrent send conflicts.
  • Extensibility is critical: new decorators should be added without modifying existing code.
๐Ÿ“‹
Guided Steps
1/5
Step 1
Define the Base Notification Interface
Design the base Notification interface that declares the send() method. Consider whether it should hold any state or just behavior.
๐Ÿ’ก Hint
In Java, interfaces cannot hold instance state (fields). Use interface for pure behavior declaration.
Use interface Notification with method public void send(); interface chosen because it declares behavior without state, enabling flexible implementation.
Mid: Recognizes Decorator pattern requires a common interface. Senior: Explains interface cannot hold state in Java, so concrete classes or decorators must hold state.
โš ๏ธCommon Wrong Answer
Using abstract class for Notification with state leads to unnecessary inheritance rigidity and violates open-closed principle.
Step 2
Implement Concrete Notification Class
Implement a concrete class BasicNotification that implements Notification and holds message content. Decide on synchronization for send().
๐Ÿ’ก Hint
BasicNotification holds instance state (message). Use synchronized send() to ensure thread-safe sending.
Concrete class BasicNotification holds message state; send() declared public synchronized void send() to atomically send notification and prevent race conditions.
Mid: Implements concrete Notification. Senior: Explains synchronized on send() prevents interleaved sends causing message corruption or duplication.
โš ๏ธCommon Wrong Answer
Omitting synchronization on send() causes race conditions where multiple threads can send partial or corrupted messages.
Step 3
Design Abstract Decorator Class
Create an abstract decorator class that implements Notification and holds a reference to a wrapped Notification object. Explain why abstract class is used here.
๐Ÿ’ก Hint
Abstract class holds instance state (wrapped Notification) and implements common forwarding behavior.
abstract class NotificationDecorator implements Notification holds protected Notification wrapped; abstract because it holds state and provides default send() forwarding; interface cannot hold fields in Java.
Mid: Identifies Decorator pattern structure. Senior: Explains abstract class needed to hold wrapped Notification reference and share forwarding logic.
โš ๏ธCommon Wrong Answer
Using interface for decorator causes duplicated forwarding code and inability to hold wrapped Notification reference.
Step 4
Implement Concrete Decorators with Additional Behavior
Implement concrete decorators like EncryptionDecorator and LoggingDecorator that extend NotificationDecorator and add behavior before/after forwarding send(). Discuss thread safety.
๐Ÿ’ก Hint
Each decorator holds its own state (e.g., encryption key). Override send() with public synchronized void send() to ensure thread-safe behavior chaining.
Concrete decorators override public synchronized void send() to add behavior and call wrapped.send(); synchronized ensures atomic execution of combined behaviors in multithreaded context.
Mid: Implements decorators adding behavior. Senior: Explains synchronized on send() in decorators prevents interleaved partial behavior execution causing inconsistent state.
โš ๏ธCommon Wrong Answer
Not synchronizing send() in decorators leads to race conditions where partial encryption or logging occurs concurrently, corrupting output.
Step 5
Support Dynamic Composition of Multiple Decorators
Explain how multiple decorators can be composed dynamically at runtime and how this design adheres to the Open-Closed Principle.
๐Ÿ’ก Hint
Each decorator wraps a Notification object, allowing recursive composition without modifying existing classes.
Dynamic composition via constructor injection of Notification into decorators; adheres to Open-Closed Principle by allowing new decorators without modifying existing code.
Mid: Describes recursive wrapping of Notification objects. Senior: Explains how composition avoids inheritance explosion and supports runtime behavior extension.
โš ๏ธCommon Wrong Answer
Using inheritance chains for each behavior combination leads to class explosion and violates Open-Closed Principle.
๐Ÿ”Œ
API Design Challenge
Design the Notification interface and decorator classes to allow dynamic addition of behaviors like encryption and logging without inheritance. Ensure thread-safe sending of notifications in a multithreaded environment.
  • Use a Java interface for Notification declaring the send() method without state.
  • Implement an abstract decorator class holding a wrapped Notification reference to forward calls.
  • Synchronize send() methods in concrete classes and decorators to prevent race conditions.
๐Ÿ“Š
Scoring Rubric
โ–ผ Show
Must Have - 60%
  • Notification interface with public void send() - interface chosen because it cannot hold state in Java
  • BasicNotification concrete class with message state and public synchronized void send() for thread safety
  • abstract class NotificationDecorator holding wrapped Notification reference - abstract class needed to hold state
  • Concrete decorators overriding public synchronized void send() to add behavior and call wrapped.send()
  • Dynamic composition of decorators via constructor injection supporting Open-Closed Principle
Good to Have - 30%
  • Explanation of synchronized send() preventing race conditions in multithreaded sends
  • Discussion of instance state in decorators (e.g., encryption key) requiring abstract class over interface
  • Use of delegation and composition to avoid inheritance explosion
  • Clear separation of concerns in decorators for single responsibility
  • Mention of layering order affecting behavior execution
Bonus - 10%
  • Use of volatile or AtomicReference if decorator references need to be updated atomically
โœ…
Model Answer
โ–ผ Reveal

This design uses the Decorator Pattern in Java to add behaviors dynamically to Notification objects without inheritance. The Notification interface declares the send() method without state. BasicNotification implements Notification and holds message state with synchronized send() for thread safety. An abstract NotificationDecorator class holds a wrapped Notification reference and forwards send() calls. Concrete decorators like EncryptionDecorator and LoggingDecorator extend NotificationDecorator, override synchronized send() to add behavior, and call wrapped.send(). Multiple decorators can be composed dynamically via constructor injection, supporting the Open-Closed Principle. Synchronization ensures thread-safe execution across the decorator chain.

  • Use Java interface for Notification to declare behavior without state, enabling flexible implementations.
  • BasicNotification holds message content and synchronizes send() to prevent race conditions during sending.
  • Abstract NotificationDecorator class holds wrapped Notification reference and provides default forwarding logic; abstract class needed to hold state as interfaces cannot.
  • Concrete decorators override synchronized send() to add behavior before/after forwarding, ensuring atomic execution in multithreaded contexts.
  • Dynamic composition via constructor injection supports layering multiple decorators at runtime without modifying existing classes, adhering to Open-Closed Principle.
  • Synchronization on send() methods across all classes prevents interleaved partial executions and inconsistent states in concurrent environments.
  • Each decorator holds its own instance state (e.g., encryption key), which is impossible in interfaces in Java, justifying abstract class use.
  • Notification interface declares public void send(); no state to allow flexible implementations.
  • BasicNotification concrete class holds message state and uses public synchronized void send() to ensure thread-safe sending.
  • abstract class NotificationDecorator holds protected Notification wrapped; provides default forwarding in send().
  • Concrete decorators override public synchronized void send() to add behavior and call wrapped.send(); synchronization prevents race conditions.
  • Dynamic composition via constructor injection supports layering multiple decorators at runtime without modifying existing classes.
  • Open-Closed Principle enforced by allowing new decorators to extend NotificationDecorator without changing existing code.
  • Use of synchronized methods ensures atomic execution of send() across decorator chain preventing interleaved partial executions.
  • Avoid inheritance explosion by favoring composition and delegation in decorators.
  • Each decorator holds its own instance state (e.g., encryption key), which is impossible in interfaces in Java.
  • Thread safety achieved by synchronized send() methods rather than coarse-grained locking or external synchronization.

Practice

(1/5)
1. In the Ride Booking system, the FareCalculator 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. Factory pattern, because it creates FareCalculator instances on demand.
B. Prototype pattern, to clone FareCalculator instances for thread safety.
C. Singleton pattern, to ensure only one FareCalculator instance calculates fares consistently.
D. Builder pattern, to construct FareCalculator with complex parameters.

Solution

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

    The private constructor prevents external instantiation, and the static synchronized getInstance() method ensures a single shared instance, matching Singleton pattern characteristics.
  2. Step 2: Why Singleton fits FareCalculator

    FareCalculator needs consistent fare rules and shared state (e.g., pricing configs). Singleton avoids multiple inconsistent instances and ensures thread-safe access.
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    Private constructor + synchronized getInstance() -> Singleton pattern [check]
Hint: Private constructor + static getInstance() = Singleton [check]
Common Mistakes:
2. You want to add a WeekendFeeStrategy to calculate parking fees differently on weekends using the current adapter-based design. What is the minimum change required to comply with SOLID principles?
medium
A. Add a boolean flag to Vehicle to indicate weekend and modify ParkingLot to check it.
B. Create a new WeekendFeeStrategy class implementing FeeStrategy and inject it where needed without modifying existing classes.
C. Modify the existing FeeStrategy class to add weekend logic with if-else checks.
D. Add weekend fee logic inside the adapter class that adapts Vehicle to ParkingLot.

Solution

  1. Step 1: Identify SOLID principle involved

    Open-Closed Principle (OCP) requires extending behavior without modifying existing code.
  2. Step 2: Evaluate options

    Create a new WeekendFeeStrategy class implementing FeeStrategy and inject it where needed without modifying existing classes. respects OCP by adding a new class implementing FeeStrategy. Add a boolean flag to Vehicle to indicate weekend and modify ParkingLot to check it. modifies Vehicle and ParkingLot, violating OCP. Options A and D modify existing classes.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    New strategy class extends behavior without modifying existing code [check]
Hint: OCP: extend with new class, don't modify existing ones [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. In a plugin loader, the loadPlugin() method checks if a plugin is already loaded before loading it. Without synchronizing this method, what exact thread interleaving causes the same plugin to be loaded twice?
medium
A. Thread 1 checks isLoaded() -> false, context switch; Thread 2 checks isLoaded() -> false; both proceed to load the plugin.
B. Thread 1 and Thread 2 both wait on a lock, causing deadlock.
C. Thread 1 loads plugin, but Thread 2 never sees the updated loaded state due to caching.
D. Thread 1 and Thread 2 both call loadPlugin() simultaneously and both throw exceptions.

Solution

  1. Step 1: Trace the check-then-act sequence without synchronization

    Thread 1 calls isLoaded() and sees false, then context switches before loading.
  2. Step 2: Thread 2 also calls isLoaded() and sees false, both threads proceed to load plugin concurrently, causing duplicate loading.

  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Unsynchronized check-then-act allows double loading due to race [check]
Hint: Check-then-act without sync -> double load race condition [check]
Common Mistakes:
5. LinkedIn wants to add a WeekendFeeStrategy for job posting fees without modifying existing classes. According to SOLID principles, what is the minimum change required to support this?
medium
A. Extend FeeCalculator with a subclass that overrides fee calculation for weekends.
B. Modify the existing FeeCalculator class to add weekend fee logic with an if-else block.
C. Add a boolean flag isWeekend to JobPosting and handle fees internally.
D. Create a new class implementing FeeStrategy for weekend fees and inject it where needed.

Solution

  1. Step 1: Identify the SOLID principle involved

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

    Creating a new WeekendFeeStrategy class implementing the existing FeeStrategy interface allows extension without modifying existing classes.
  3. Final Answer:

    Option D -> Option D
  4. Quick Check:

    New FeeStrategy class extends behavior without modifying existing code [check]
Hint: OCP: add 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