Bird
Raised Fist0
LLDsystem_design~15 mins

Decorator pattern in LLD - Deep Dive

Choose your learning style10 modes available
Overview - Decorator pattern
What is it?
The Decorator pattern is a way to add new features to an object without changing its original code. It wraps the object with another object that adds the extra behavior. This lets you mix and match features dynamically, like putting layers on a cake. It helps keep code flexible and easy to extend.
Why it matters
Without the Decorator pattern, adding new features means changing existing code, which can cause bugs and make the system hard to maintain. This pattern solves that by letting you add features on the fly, keeping the core code clean and stable. It makes software easier to grow and adapt to new needs without breaking old parts.
Where it fits
Before learning the Decorator pattern, you should understand basic object-oriented programming concepts like classes, objects, and interfaces. After this, you can explore other design patterns like Proxy and Adapter, which also deal with wrapping objects but for different purposes.
Mental Model
Core Idea
The Decorator pattern wraps an object to add new behavior without changing the original object itself.
Think of it like...
It's like putting a jacket on a person to keep them warm without changing who they are underneath.
┌─────────────┐       ┌─────────────┐       ┌─────────────┐
│  Client     │──────▶│ Decorator   │──────▶│  Component  │
└─────────────┘       └─────────────┘       └─────────────┘
       ▲                    ▲                    ▲
       │                    │                    │
       │                    │                    │
       │             ┌─────────────┐             │
       │             │ Concrete    │             │
       │             │ Decorator   │             │
       │             └─────────────┘             │
       │                                        │
       └────────────────────────────────────────┘
Build-Up - 6 Steps
1
FoundationUnderstanding basic object wrapping
🤔
Concept: Learn how one object can hold a reference to another to extend behavior.
Imagine you have a simple object that does a task. Wrapping means creating a new object that holds the original one inside it. When you call a method on the wrapper, it can do extra work before or after calling the original object's method.
Result
You get an object that behaves like the original but can add new actions around it.
Understanding object wrapping is the foundation of the Decorator pattern because it shows how behavior can be layered without changing the original.
2
FoundationInterface consistency for interchangeable objects
🤔
Concept: The wrapper and the original object share the same interface so they can replace each other.
Both the original object and the wrapper implement the same set of methods. This means the client code can use either without knowing which one it has. This is key to making decorators transparent and flexible.
Result
Clients can use decorated or undecorated objects interchangeably without code changes.
Keeping the interface consistent allows seamless swapping and stacking of decorators.
3
IntermediateStacking multiple decorators
🤔Before reading on: do you think you can add multiple decorators on one object at the same time? Commit to yes or no.
Concept: Decorators can wrap other decorators, creating layers of added behavior.
You can wrap an object with one decorator, then wrap that decorator with another, and so on. Each layer adds its own behavior, and calls pass through all layers down to the original object.
Result
You get a flexible chain of behaviors that can be combined in different orders.
Knowing decorators can stack helps you build complex features by combining simple ones.
4
IntermediateDynamic behavior addition at runtime
🤔Before reading on: do you think decorators can be added or removed while the program runs, or only at design time? Commit to your answer.
Concept: Decorators allow adding features dynamically while the program is running.
Because decorators wrap objects, you can choose which decorators to apply based on runtime conditions. This means you can add or remove features without restarting or changing the original code.
Result
Your system becomes more adaptable and responsive to changing needs.
Understanding runtime flexibility shows why decorators are powerful for real-world applications.
5
AdvancedAvoiding tight coupling with composition
🤔Before reading on: do you think decorators inherit from the original object or contain it? Commit to your answer.
Concept: Decorators use composition (containment) instead of inheritance to add behavior.
Instead of creating subclasses, decorators hold a reference to the original object and forward calls to it. This avoids the problems of deep inheritance hierarchies and makes the system easier to extend.
Result
You get more flexible and maintainable code that can grow without becoming rigid.
Knowing decorators rely on composition helps avoid common design pitfalls with inheritance.
6
ExpertPerformance and complexity trade-offs in deep decorator chains
🤔Before reading on: do you think adding many decorators always improves design without downsides? Commit to yes or no.
Concept: Using many decorators can add overhead and make debugging harder.
Each decorator adds a layer of method calls and complexity. Deep chains can slow down performance and make it difficult to trace behavior. Experts balance the benefits of flexibility with these costs and use tools to manage complexity.
Result
You learn to design decorator chains thoughtfully, avoiding excessive layering.
Understanding trade-offs prevents overusing decorators and keeps systems efficient and maintainable.
Under the Hood
At runtime, a decorator object holds a reference to the original object and implements the same interface. When a method is called on the decorator, it can execute additional code before or after delegating the call to the original object. This chaining can continue through multiple decorators, each adding its own behavior. The key is that the decorator forwards calls, preserving the original object's interface and behavior while extending it.
Why designed this way?
The Decorator pattern was created to solve the problem of extending object behavior without using inheritance, which can lead to rigid and complex class hierarchies. By using composition and delegation, it allows flexible, reusable, and dynamic feature addition. Alternatives like subclassing were rejected because they require changes at compile time and can cause class explosion when many feature combinations are needed.
┌─────────────┐       ┌─────────────┐       ┌─────────────┐
│  Client     │──────▶│ Decorator   │──────▶│  Component  │
│             │       │ (wraps comp)│       │ (original)  │
└─────────────┘       └─────────────┘       └─────────────┘
       │                    │                    ▲
       │                    │                    │
       │                    └────────────────────┘
       │
       │
       ▼
┌─────────────┐
│ Concrete    │
│ Decorator   │
│ (adds feat) │
└─────────────┘
Myth Busters - 4 Common Misconceptions
Quick: Does the Decorator pattern change the original object's code? Commit to yes or no.
Common Belief:Decorators modify the original object's code to add new features.
Tap to reveal reality
Reality:Decorators do not change the original object; they wrap it to add behavior externally.
Why it matters:Believing decorators change original code leads to confusion about their purpose and misuse of inheritance instead of composition.
Quick: Can decorators only add behavior before the original method call? Commit to yes or no.
Common Belief:Decorators can only add behavior before calling the original method.
Tap to reveal reality
Reality:Decorators can add behavior before, after, or even replace the original method call.
Why it matters:Limiting decorators to only pre-call behavior restricts their usefulness and design flexibility.
Quick: Is it always better to use many decorators for all features? Commit to yes or no.
Common Belief:More decorators always mean better design and flexibility.
Tap to reveal reality
Reality:Too many decorators can cause performance issues and make debugging difficult.
Why it matters:Overusing decorators can lead to complex, slow, and hard-to-maintain systems.
Quick: Do decorators require the wrapped object to be a subclass? Commit to yes or no.
Common Belief:Decorators must inherit from the object they wrap.
Tap to reveal reality
Reality:Decorators use composition, not inheritance, to wrap objects.
Why it matters:Confusing inheritance with composition leads to rigid designs and defeats the pattern's purpose.
Expert Zone
1
Decorators can maintain state independently from the wrapped object, enabling feature-specific data without polluting the original.
2
The order of applying decorators matters; different sequences can produce different behaviors and side effects.
3
Some decorators can conditionally delegate calls, selectively enabling or disabling features at runtime.
When NOT to use
Avoid the Decorator pattern when the added behavior is static and known at compile time; in such cases, subclassing or simple inheritance may be simpler. Also, if performance is critical and the overhead of multiple layers is unacceptable, consider alternative designs like aspect-oriented programming or compile-time code generation.
Production Patterns
In real systems, decorators are used for logging, security checks, caching, and UI enhancements. For example, a web server might decorate request handlers with authentication and rate limiting layers. Professionals also use decorators to implement cross-cutting concerns without scattering code.
Connections
Proxy pattern
Both wrap objects to control access, but Proxy focuses on controlling access while Decorator adds behavior.
Understanding Proxy helps clarify that Decorator is about adding features, not just controlling or restricting access.
Functional composition (Mathematics)
Decorator chaining is like composing functions where output of one feeds into another.
Seeing decorators as function compositions helps grasp how behaviors layer and combine.
Layered clothing (Fashion)
Both involve adding layers to achieve new effects without changing the base layer.
Recognizing layering in fashion helps appreciate the flexibility and modularity of decorators.
Common Pitfalls
#1Adding behavior by modifying the original object's code directly.
Wrong approach:class Component { operation() { // original behavior } addFeature() { // new behavior added here } }
Correct approach:class Decorator { constructor(component) { this.component = component; } operation() { // new behavior this.component.operation(); } }
Root cause:Misunderstanding that decorators add behavior externally rather than changing original code.
#2Using inheritance to add features instead of composition.
Wrong approach:class ExtendedComponent extends Component { operation() { // added behavior super.operation(); } }
Correct approach:class Decorator { constructor(component) { this.component = component; } operation() { // added behavior this.component.operation(); } }
Root cause:Confusing subclassing with the decorator's composition-based approach.
#3Stacking decorators without considering order effects.
Wrong approach:let decorated = new DecoratorB(new DecoratorA(component)); // order ignored
Correct approach:let decorated = new DecoratorA(new DecoratorB(component)); // order chosen carefully
Root cause:Not realizing that the sequence of decorators changes the final behavior.
Key Takeaways
The Decorator pattern adds new behavior to objects by wrapping them, not by changing their code.
It relies on composition and interface consistency to allow flexible and dynamic feature addition.
Multiple decorators can be stacked to combine behaviors, but order and performance must be managed carefully.
This pattern helps keep code maintainable and adaptable by avoiding rigid inheritance hierarchies.
Understanding when and how to use decorators is key to building scalable and clean software systems.

Practice

(1/5)
1. What is the main purpose of the Decorator pattern in system design?
easy
A. To create multiple instances of a class
B. To add new features to objects without changing their original code
C. To replace an object with a completely new one
D. To store objects in a database

Solution

  1. Step 1: Understand the Decorator pattern goal

    The Decorator pattern is designed to add new behavior or features to objects dynamically without modifying their original code.
  2. Step 2: Compare options with the pattern goal

    Options B, C, and D do not describe adding features without changing code. Only To add new features to objects without changing their original code matches the pattern's purpose.
  3. Final Answer:

    To add new features to objects without changing their original code -> Option B
  4. Quick Check:

    Decorator pattern = add features without code change [OK]
Hint: Decorator adds features by wrapping, not by changing code [OK]
Common Mistakes:
  • Confusing Decorator with Factory pattern
  • Thinking it replaces objects instead of wrapping
  • Assuming it stores data instead of adding behavior
2. Which of the following is the correct way to implement a decorator in a class-based design?
easy
A. Use a global variable to store extra features
B. Modify the original class to include new methods directly
C. Delete the original class and write a new one with extra features
D. Create a new class that wraps the original class and adds new methods

Solution

  1. Step 1: Recall Decorator implementation style

    Decorator pattern uses a wrapper class that holds an instance of the original class and adds new behavior.
  2. Step 2: Evaluate each option

    Create a new class that wraps the original class and adds new methods correctly describes wrapping with a new class. Options A, B, and C do not follow the decorator pattern principles.
  3. Final Answer:

    Create a new class that wraps the original class and adds new methods -> Option D
  4. Quick Check:

    Decorator = wrapper class adding methods [OK]
Hint: Decorator wraps original class; never modifies it directly [OK]
Common Mistakes:
  • Changing original class instead of wrapping
  • Replacing class instead of extending behavior
  • Using global variables for decoration
3. Consider this pseudocode using the Decorator pattern:
class Coffee:
    def cost(self):
        return 5

class MilkDecorator:
    def __init__(self, coffee):
        self.coffee = coffee
    def cost(self):
        return self.coffee.cost() + 2

order = MilkDecorator(Coffee())
print(order.cost())

What will be the output of print(order.cost())?
medium
A. 7
B. 5
C. 2
D. Error

Solution

  1. Step 1: Understand the base cost

    The Coffee class returns 5 when cost() is called.
  2. Step 2: Calculate decorated cost

    The MilkDecorator adds 2 to the coffee cost by calling self.coffee.cost() and adding 2.
  3. Final Answer:

    7 -> Option A
  4. Quick Check:

    5 + 2 = 7 [OK]
Hint: Decorator adds to base cost by calling wrapped object's method [OK]
Common Mistakes:
  • Forgetting to call wrapped object's method
  • Returning only decorator's added cost
  • Confusing base and added costs
4. In the following code snippet, what is the main issue that prevents the decorator from working correctly?
class LoggerDecorator:
    def __init__(self, component):
        self.component = component
    def execute(self):
        print('Logging start')
        self.execute()
        print('Logging end')
medium
A. The decorator calls itself recursively causing infinite loop
B. The decorator does not call the wrapped component's execute method
C. The constructor is missing a parameter
D. The print statements are incorrect

Solution

  1. Step 1: Analyze method calls inside execute()

    The execute() method calls self.execute(), which calls itself recursively without a base case.
  2. Step 2: Identify correct call to wrapped component

    The decorator should call self.component.execute() to delegate the call, not call itself.
  3. Final Answer:

    The decorator calls itself recursively causing infinite loop -> Option A
  4. Quick Check:

    Recursive call without base case = infinite loop [OK]
Hint: Decorator must call wrapped object's method, not itself [OK]
Common Mistakes:
  • Calling self.execute() instead of wrapped component
  • Missing delegation to wrapped object
  • Ignoring recursion issues
5. You are designing a notification system where messages can be sent via Email, SMS, and Push notifications. You want to add features like encryption and logging dynamically without changing the core notification classes. How would you apply the Decorator pattern here?
hard
A. Use inheritance to create subclasses for every combination of features
B. Add encryption and logging code directly inside each notification class
C. Create decorator classes for encryption and logging that wrap notification objects
D. Store all notifications in a database and process features later

Solution

  1. Step 1: Identify need for dynamic feature addition

    Encryption and logging should be added without modifying core notification classes, so dynamic wrapping is needed.
  2. Step 2: Apply Decorator pattern for flexible feature addition

    Create decorator classes that wrap notification objects and add encryption or logging behavior dynamically.
  3. Final Answer:

    Create decorator classes for encryption and logging that wrap notification objects -> Option C
  4. Quick Check:

    Decorator pattern = dynamic wrapping for features [OK]
Hint: Wrap notifications with decorators for features, avoid code changes [OK]
Common Mistakes:
  • Modifying core classes directly
  • Using inheritance explosion for feature combos
  • Ignoring dynamic behavior needs
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