Bird
Raised Fist0
Interview Prepoop-design-patternsmediumAmazonGoogleFlipkartSwiggyCRED

Decorator Pattern - Wrapping Behaviour Without Subclassing

Choose your preparation mode3 modes available
▶
Steps
setup

Define Abstract Component Interface Coffee

The abstract class Coffee is defined with abstract methods cost() and description(). This sets the contract for all coffee-related classes.

💡 This step establishes the interface that all concrete coffees and decorators must implement, ensuring consistent behavior.
Line:class Coffee(ABC): @abstractmethod def cost(self): pass
💡 All coffee types will share these methods, enabling decorators to wrap any Coffee object interchangeably.
📊
Decorator Pattern - Wrapping Behaviour Without Subclassing - Watch the Algorithm Execute, Step by Step
Watching this step-by-step helps you understand how decorators add behavior without subclassing, by wrapping objects and forwarding method calls.
Step 1/10
·Active fill★Answer cell
Defines the abstract component interface for the Decorator pattern.
«abstract»Coffee
+cost()
+description()
Concrete component implements the component interface.
«abstract»Coffee
+cost()
+description()
SimpleCoffee
+cost()
+description()
SimpleCoffee ▷ Coffee (1:1)
Abstract decorator holds a reference to a Coffee component and delegates calls.
«abstract»Coffee
+cost()
+description()
SimpleCoffee
+cost()
+description()
CoffeeDecorator
−_coffee: Coffee
+__init__()
+cost()
+description()
SimpleCoffee ▷ Coffee (1:1)CoffeeDecorator ▷ Coffee (1:1)
Concrete decorator adds new behavior and state.
«abstract»Coffee
+cost()
+description()
SimpleCoffee
+cost()
+description()
CoffeeDecorator
−_coffee: Coffee
+__init__()
+cost()
+description()
MilkDecorator
−amount: int
+__init__()
+cost()
+description()
SimpleCoffee ▷ Coffee (1:1)CoffeeDecorator ▷ Coffee (1:1)MilkDecorator ▷ CoffeeDecorator (1:1)
Another concrete decorator adds behavior independently.
«abstract»Coffee
+cost()
+description()
SimpleCoffee
+cost()
+description()
CoffeeDecorator
−_coffee: Coffee
+__init__()
+cost()
+description()
MilkDecorator
−amount: int
+__init__()
+cost()
+description()
SugarDecorator
−amount: int
+__init__()
+cost()
+description()
SimpleCoffee ▷ Coffee (1:1)CoffeeDecorator ▷ Coffee (1:1)MilkDecorator ▷ CoffeeDecorator (1:1)SugarDecorator ▷ CoffeeDecorator (1:1)
Instantiated concrete component object.
SimpleCoffee
+cost()
+description()
Decorator wraps component to add milk behavior.
MilkDecorator
−_coffee: SimpleCoffee
−amount: int
+cost()
+description()
SimpleCoffee
+cost()
+description()
MilkDecorator ◆ SimpleCoffee (1:1)
Stacked decorators compose behavior dynamically.
SugarDecorator
−_coffee: MilkDecorator
−amount: int
+cost()
+description()
MilkDecorator
−_coffee: SimpleCoffee
−amount: int
+cost()
+description()
SimpleCoffee
+cost()
+description()
SugarDecorator ◆ MilkDecorator (1:1)MilkDecorator ◆ SimpleCoffee (1:1)
Delegation chain for description method.
SugarDecorator
−_coffee: MilkDecorator
−amount: int
+description()
MilkDecorator
−_coffee: SimpleCoffee
−amount: int
+description()
SimpleCoffee
+description()
SugarDecorator ◆ MilkDecorator (1:1)MilkDecorator ◆ SimpleCoffee (1:1)
Delegation chain for cost method.
SugarDecorator
−_coffee: MilkDecorator
−amount: int
+cost()
MilkDecorator
−_coffee: SimpleCoffee
−amount: int
+cost()
SimpleCoffee
+cost()
SugarDecorator ◆ MilkDecorator (1:1)MilkDecorator ◆ SimpleCoffee (1:1)

Key Takeaways

✓ Decorators wrap objects to add behavior dynamically without subclassing.

This is hard to see from code alone because the wrapping and delegation happen at runtime and are invisible in static class hierarchies.

✓ Method calls delegate through the decorator chain, accumulating behavior step-by-step.

Visualizing each call clarifies how each decorator contributes to the final result.

✓ Stacking multiple decorators composes complex behavior flexibly and transparently.

The trace shows how each decorator wraps the previous one, enabling modular extensions.

Practice

(1/5)
1. When a class implements an interface and also extends an abstract class, what is the sequence of abstraction enforcement and implementation that occurs during object instantiation?
easy
A. The abstract class constructor runs, but interface methods have no implementation to run.
B. Interface methods are implemented first, then the abstract class constructor runs.
C. The class must implement interface methods before the abstract class constructor runs.
D. The abstract class's constructor runs first, then interface methods are implemented by the class.

Solution

  1. Step 1: Understand interface role

    Interfaces declare methods but provide no implementation or constructors.
  2. Step 2: Abstract class constructor behavior

    Abstract class constructors run during instantiation to initialize shared state.
  3. Step 3: Implementation of interface methods

    The class implementing the interface provides method bodies; no constructor or code runs from interface itself.
  4. Final Answer:

    Option A -> Option A
  5. Quick Check:

    Interfaces define contracts only; abstract class constructors run during instantiation.
Hint: Interface methods have no constructor or implementation to run; abstract class constructors always run.
Common Mistakes:
  • Thinking interface methods have constructors or code to execute.
  • Believing interface implementation order affects constructor execution.
  • Confusing interface method implementation with constructor invocation.
2. You are designing a system that manages user accounts and sends notification emails. According to the Single Responsibility Principle, how should you organize these responsibilities?
easy
A. Separate user account management and email notification into different classes because each has a different reason to change.
B. Combine user account management and email notification in one class because they are related to users.
C. Put all user-related functionalities, including notifications, into a single class to reduce the number of classes.
D. Create one class for user management and embed email notification logic inside its methods to simplify interactions.

Solution

  1. Step 1: Identify reasons to change

    User account management changes when user data or authentication changes; email notifications change when messaging or delivery requirements change.
  2. Step 2: Apply SRP

    Since these reasons to change differ, they should be separated into different classes to avoid coupling unrelated changes.
  3. Step 3: Evaluate other options

    Options A, C, and D combine responsibilities, increasing coupling and reducing cohesion, violating SRP.
  4. Final Answer:

    Option A -> Option A
  5. Quick Check:

    Separate classes for distinct reasons to change -> SRP compliant.
Hint: One class, one reason to change.
Common Mistakes:
  • Assuming related domain means same responsibility.
  • Combining functionalities to reduce class count.
  • Embedding multiple responsibilities for convenience.
3. Which of the following statements about using inheritance to model different vehicle types in the parking lot system is INCORRECT?
medium
A. Inheritance allows sharing common vehicle attributes and behaviors in a base Vehicle class
B. Using inheritance for vehicle types can lead to rigid designs that are hard to extend with new types
C. Inheritance is always the best approach to add new vehicle types without modifying existing code
D. Polymorphism enables treating all vehicle types uniformly when allocating parking spots

Solution

  1. Step 1: Review inheritance benefits

    Inheritance supports code reuse and polymorphism for uniform handling.
  2. Step 2: Recognize limitations

    Inheritance hierarchies can become rigid and hard to extend, violating open-closed principle.
  3. Step 3: Identify incorrect statement

    Claiming inheritance is always best ignores alternatives like composition or interfaces that improve extensibility.
  4. Final Answer:

    Option C -> Option C
  5. Quick Check:

    Inheritance is not always best for extensibility [OK]
Hint: Inheritance can cause rigidity; prefer composition for extensibility [OK]
Common Mistakes:
  • Assuming inheritance is always the best design choice
  • Ignoring polymorphism benefits
4. What is the time complexity of notifying observers in the thread-safe observer pattern implementation where observers are stored in a dictionary mapping event types to sets of observers, and a snapshot list is created during notification?
medium
A. O(n) where n is total number of all observers across all event types
B. O(k) where k is the number of observers subscribed to the notified event type
C. O(k + n) where k is observers for event type and n is total observers
D. O(1) constant time due to hash set usage

Solution

  1. Step 1: Identify the data structure and notification process

    Observers are stored per event type in sets. Notification locks and copies only the observers for the specific event type.
  2. Step 2: Analyze complexity of notify()

    Notify creates a snapshot list of observers for the event type (size k) and iterates over it, so time is proportional to k, not total n.
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    Notify cost depends only on observers for that event type [OK]
Hint: Notify complexity depends on observers for event type only [OK]
Common Mistakes:
  • Assuming notify iterates over all observers regardless of event type
5. In a legacy system, a class handles both business logic and logging. You want to refactor it following SRP, but the logging code is tightly intertwined with business logic. What is the best approach to refactor this while respecting SRP?
hard
A. Ignore SRP in this case because legacy code should not be refactored.
B. Leave logging inside the class because extracting it would break existing functionality and increase risk.
C. Merge business logic and logging into a utility class to centralize all cross-cutting concerns.
D. Extract logging into a separate class and replace logging calls with calls to this new class, even if it requires modifying many places.

Solution

  1. Step 1: Identify responsibilities

    Business logic and logging are separate reasons to change.
  2. Step 2: Refactor strategy

    Extract logging into its own class to isolate changes related to logging.
  3. Step 3: Address tight coupling

    Though intertwined, refactoring calls to the new logging class improves maintainability and respects SRP.
  4. Step 4: Evaluate other options

    Options B and D avoid refactoring, risking future issues; C increases coupling by merging unrelated concerns.
  5. Final Answer:

    Option D -> Option D
  6. Quick Check:

    Extract and isolate responsibilities even if it requires effort -> SRP compliance.
Hint: Separate concerns even if intertwined; refactor stepwise.
Common Mistakes:
  • Avoiding refactoring due to perceived risk.
  • Merging unrelated concerns for convenience.
  • Ignoring SRP in legacy code.
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