Bird
Raised Fist0
LLDsystem_design~7 mins

Decorator pattern in LLD - System Design Guide

Choose your learning style10 modes available
Problem Statement
When you want to add new features to an object without changing its original code, modifying the base class directly can cause bugs and make the system hard to maintain. Also, subclassing for every new feature leads to a large, complex inheritance tree that is difficult to manage and extend.
Solution
The Decorator pattern wraps the original object inside another object that adds new behavior before or after delegating calls to the original. This way, you can layer multiple decorators dynamically without changing the original object's code or creating many subclasses.
Architecture
Client
→Decorator
↓
Concrete
Concrete

This diagram shows the client calling a decorator object, which wraps the original component. The decorator can add behavior before or after forwarding calls to the original component.

Trade-offs
✓ Pros
→
Allows adding responsibilities to objects at runtime without modifying their code.
→
Supports layering multiple decorators to combine behaviors flexibly.
→
Keeps classes simple and focused by separating concerns into decorators.
→
Avoids explosion of subclasses compared to inheritance-based extensions.
✗ Cons
→
Can result in many small decorator classes, increasing complexity.
→
Debugging can be harder because behavior is spread across layers.
→
Performance overhead due to multiple wrapper calls in deep decorator chains.
Use when you need to add features to objects dynamically and want to avoid subclass explosion, especially when behaviors can be combined in different ways.
Avoid when the system is simple with few variations, or when performance is critical and the overhead of multiple wrappers is unacceptable.
Real World Examples
Python standard library
The I/O library uses decorators to add buffering, encoding, and compression to file streams without changing the base stream classes.
Java I/O API
Java uses decorators like BufferedInputStream and DataInputStream to add features to basic InputStream objects dynamically.
Flask web framework
Flask uses decorators to add routes and middleware behaviors to functions without modifying the original function code.
Code Example
The before code uses subclassing to add milk and sugar costs, leading to many subclasses for combinations. The after code wraps the base Coffee object with decorators that add cost dynamically, allowing flexible feature combinations without subclass explosion.
LLD
### Before (without decorator pattern):
class Coffee:
    def cost(self):
        return 5

class MilkCoffee(Coffee):
    def cost(self):
        return super().cost() + 2

class SugarMilkCoffee(MilkCoffee):
    def cost(self):
        return super().cost() + 1

coffee = SugarMilkCoffee()
print(coffee.cost())  # Output: 8


### After (with decorator pattern):
class Coffee:
    def cost(self):
        return 5

class CoffeeDecorator:
    def __init__(self, coffee):
        self._coffee = coffee
    def cost(self):
        return self._coffee.cost()

class MilkDecorator(CoffeeDecorator):
    def cost(self):
        return self._coffee.cost() + 2

class SugarDecorator(CoffeeDecorator):
    def cost(self):
        return self._coffee.cost() + 1

coffee = Coffee()
coffee_with_milk = MilkDecorator(coffee)
coffee_with_milk_and_sugar = SugarDecorator(coffee_with_milk)
print(coffee_with_milk_and_sugar.cost())  # Output: 8
OutputSuccess
Alternatives
Subclassing
Creates new classes for each feature combination at compile time, leading to many subclasses.
Use when: When features are fixed and few, and runtime flexibility is not needed.
Proxy pattern
Focuses on controlling access to an object rather than adding behavior.
Use when: When you need to control or manage access rather than extend functionality.
Aspect-Oriented Programming (AOP)
Injects cross-cutting concerns like logging or security across many classes transparently.
Use when: When you want to apply behaviors across many unrelated classes without manual wrapping.
Summary
Decorator pattern adds new behavior to objects dynamically by wrapping them.
It avoids subclass explosion and keeps code flexible and maintainable.
It is widely used in I/O libraries and frameworks to extend functionality.

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