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
Dynamically add responsibilities to objects without subclassing by wrapping them.
easyDesign Patterns - AppliedFreq: common
Prerequisites:OOP BasicsComposition
⚙️
Functional Requirements
  • Define a Component interface or abstract class with a common operation() method.
  • ConcreteComponent implements Component with core behavior.
  • Decorator implements Component and holds a reference to a Component instance.
  • ConcreteDecorator extends Decorator and overrides operation() to add behavior before/after delegating.
  • Ensure thread-safe operation() calls when decorators modify shared state.
  • Allow stacking multiple decorators dynamically.
  • Avoid modifying existing component or decorator classes when adding new behaviors.
👤
Use Cases
  • Client creates a ConcreteComponent and wraps it with one or more Decorators to add behavior dynamically.
  • Client calls operation() on the decorated object, which delegates to wrapped components and adds extra behavior.
  • Multiple decorators can be stacked to combine behaviors without modifying existing classes.
  • Decorator transparently forwards requests to the wrapped component while adding responsibilities.
  • Support runtime addition and removal of decorators if needed.
🧑‍💻
Actors
  • Client
  • Component
  • ConcreteComponent
  • Decorator
  • ConcreteDecorator
❓
Clarifying Questions to Ask
  • How many concurrent threads will invoke operation() on decorated objects?
  • Is the decorator expected to maintain any mutable state that requires synchronization?
  • Should decorators support removal or replacement at runtime?
  • Are there any constraints on the order of decorator stacking?
  • Is there a pricing or cost model associated with adding decorators?
  • What edge cases exist if a decorator wraps null or an uninitialized component?
Out of Scope
✗ Persistence or serialization of decorated objects.
✗ Distributed or remote invocation of decorated components.
✗ GUI-specific decorators or event handling.
✗ Performance optimization beyond thread safety.
🔢
Enumerations
enum ComponentType
CONCRETE_COMPONENT | DECORATOR | CONCRETE_DECORATOR
Defines roles of classes in the pattern to prevent string typos and enable exhaustive switch statements.
enum OperationStatus
SUCCESS | FAILURE
Standardizes return status of operation() calls to handle success/failure uniformly.
📦
Component
0 fields
Defines the interface for objects that can have responsibilities added dynamically.
Why thisUsing an interface allows different implementations and supports polymorphism without inheritance.
Why notUsing concrete classes or abstract classes only would limit flexibility and increase coupling.
public OperationStatus operation()Performs the core operation; to be implemented by concrete components and decorators.
📦
ConcreteComponent
1 fields
private String name
Implements the core behavior of the component without any added responsibilities.
Why thisConcreteComponent encapsulates the original object's behavior that decorators will extend.
Why notExtending ConcreteComponent directly for behavior addition leads to class explosion and tight coupling.
public synchronized OperationStatus operation()Executes the base operation; synchronized to ensure thread safety if state is accessed.
📦
Decorator
1 fields
protected volatile Component component
Holds a reference to a Component and delegates operation calls to it, enabling behavior extension.
Why thisComposition over inheritance enables flexible runtime behavior extension without subclass explosion.
Why notInheritance would require creating many subclasses for each behavior combination, reducing maintainability.
public synchronized void setComponent(Component component)Sets or replaces the wrapped component reference atomically.
public OperationStatus operation()Delegates operation call to the wrapped component; subclasses override to add behavior.
📦
ConcreteDecoratorA
1 fields
private String addedState
Adds specific behavior before and/or after delegating to the wrapped component.
Why thisAllows adding new responsibilities without modifying existing classes, supporting Open/Closed Principle.
Why notModifying ConcreteComponent directly would violate single responsibility and increase coupling.
public synchronized OperationStatus operation()Adds behavior around the wrapped component's operation; synchronized to protect addedState.
📦
ConcreteDecoratorB
1 fields
private int counter
Adds counting behavior to track number of operation() calls.
Why thisDemonstrates stateful decoration with thread-safe counting of operations.
Why notUsing external counters or global state would reduce encapsulation and increase error risk.
public synchronized OperationStatus operation()Increments counter and delegates operation; synchronized to ensure atomic counter update.
public synchronized int getCounter()Returns the current count of operation invocations.
🔗
Relationships
ConcreteComponent──►Component[1]
inheritanceindependent
Decorator──►Component[1]
realizationindependent
ConcreteDecoratorA──►Decorator[1]
inheritanceindependent
ConcreteDecoratorB──►Decorator[1]
inheritanceindependent
Decorator◇──►Component[1]
aggregationindependent
🗄️
ComponentState
PK: componentId
componentId UUIDtimestamp TIMESTAMPstateData TEXT
Stores state snapshots of components and decorators for debugging or rollback.
🗄️
OperationLog
PK: componentId
componentId UUIDoperationId UUIDstatus TEXTinvocationTime TIMESTAMP
Logs each operation invocation status and timing for monitoring and analysis.
🔌
AddDecorator
Request
{
  "componentId": "UUID",
  "decoratorType": "String"
}
Response
{
  "success": "boolean",
  "message": "String"
}
Adds a decorator of specified type to the component at runtime.
🔌
InvokeOperation
Request
{
  "componentId": "UUID"
}
Response
{
  "status": "OperationStatus"
}
Invokes operation() on the specified component or decorated component.
🔌
GetDecoratorCount
Request
{
  "componentId": "UUID"
}
Response
{
  "count": "int"
}
Returns the number of decorators currently wrapping the component.

Practice

(1/5)
1. 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:
2. In designing the Player abstraction for the Snake and Ladder game, should Player be an abstract class or an interface, and why?
easy
A. Player should be an interface because it only defines behavior without state.
B. Player should be an abstract class because it holds common state like current position and provides default methods.
C. Player should be an interface to allow multiple inheritance of behaviors.
D. Player should be an abstract class to enforce implementation of all methods in subclasses.

Solution

  1. Step 1: Analyze state and behavior requirements

    The Player has common state such as current position on the board and methods like move().
  2. Step 2: Choose abstraction type

    An abstract class allows sharing state and default method implementations, which interfaces (pre-Java 8) cannot provide.
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    Abstract class holds shared state and behavior [check]
Hint: State + default behavior -> abstract class, not interface [check]
Common Mistakes:
3. In a parking system, should Vehicle be an abstract class or an interface if it needs to hold state like licensePlate and provide default behavior for getSize()? Why?
easy
A. Interface, because Java 8+ interfaces can have default methods and static fields
B. Interface, because it allows multiple inheritance and enforces method contracts without state
C. Abstract class, because it can hold state and provide default method implementations
D. Abstract class, because it forces subclasses to implement all methods without defaults

Solution

  1. Step 1: Analyze state and behavior requirements

    Since Vehicle needs to hold state like licensePlate and provide default behavior, it requires fields and method implementations.
  2. Step 2: Abstract class vs interface capabilities

    Abstract classes can have instance fields and concrete methods, while interfaces cannot hold instance state (only constants) and default methods cannot manage instance variables.
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    State + default behavior -> abstract class, not interface [check]
Hint: State needs abstract class; interfaces lack instance fields [check]
Common Mistakes:
4. You want to add a WeekendFeeStrategy to calculate notification fees differently on weekends. According to SOLID principles, what is the minimum change required in the current design that uses a FeeStrategy interface?
medium
A. Create a new WeekendFeeStrategy class implementing FeeStrategy and inject it without modifying existing classes.
B. Add a boolean weekend flag to FeeStrategy interface and update all implementations accordingly.
C. Modify the existing FeeStrategy implementation to add weekend logic with if-else checks.
D. Create a WeekendFeeStrategy subclass extending the existing FeeStrategy implementation.

Solution

  1. Step 1: Identify applicable SOLID principle

    The Open-Closed Principle (OCP) requires classes to be open for extension but closed for modification.
  2. Step 2: Evaluate options against OCP

    Create a new WeekendFeeStrategy class implementing FeeStrategy and inject it without modifying existing classes. adds a new class implementing FeeStrategy without modifying existing code, adhering to OCP. Others modify existing classes or interfaces, violating OCP.
  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 [check]
Common Mistakes:
5. In a multithreaded LRU Cache, if the put() method is not synchronized, which exact sequence of operations between Thread 1 and Thread 2 can cause the cache to contain duplicate keys or corrupt the doubly linked list?
medium
A. Thread 1 and Thread 2 both call get() simultaneously, causing stale reads of the cache order.
B. Thread 1 updates the value of an existing key while Thread 2 evicts a different key, causing no race condition.
C. Thread 1 removes the tail node while Thread 2 updates the head node, causing inconsistent pointers but no duplicate keys.
D. Thread 1 checks if key exists -> false, context switch; Thread 2 checks if key exists -> false; both insert new nodes for the same key concurrently.

Solution

  1. Step 1: Identify the race condition in put()

    Without synchronization, two threads can both check for key existence and find it missing before either inserts, leading to duplicate nodes for the same key.
  2. Step 2: Trace the exact interleaving

    Thread 1: check key -> false; context switch; Thread 2: check key -> false; both insert new nodes, corrupting the cache state.
  3. Final Answer:

    Option D -> Option D
  4. Quick Check:

    Concurrent check-then-act without lock -> duplicate keys inserted [check]
Hint: Check-then-act without lock causes duplicates [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