🧠
Decorator Pattern with Dynamic Behavior Injection
💡 This approach extends the decorator pattern by allowing decorators to accept parameters or functions to customize behavior dynamically, increasing flexibility.
Intuition
Decorators can be parameterized or accept lambdas to add behavior dynamically, e.g., adding variable amounts of milk or sugar or logging dynamically.
Algorithm
- Define component interface and concrete component as before.
- Create decorators that accept parameters or functions in their constructor.
- In decorator methods, use these parameters to modify behavior dynamically.
- Stack decorators as needed to compose complex behavior.
💡 This approach shows how decorators can be more than static wrappers, enabling runtime customization.
from abc import ABC, abstractmethod
class Coffee(ABC):
@abstractmethod
def cost(self):
pass
@abstractmethod
def description(self):
pass
class SimpleCoffee(Coffee):
def cost(self):
return 5
def description(self):
return "Coffee"
class CoffeeDecorator(Coffee):
def __init__(self, coffee):
self._coffee = coffee
def cost(self):
return self._coffee.cost()
def description(self):
return self._coffee.description()
class MilkDecorator(CoffeeDecorator):
def __init__(self, coffee, amount=1):
super().__init__(coffee)
self.amount = amount
def cost(self):
return super().cost() + 0.5 * self.amount
def description(self):
return super().description() + f", Milk({self.amount})"
class SugarDecorator(CoffeeDecorator):
def __init__(self, coffee, amount=1):
super().__init__(coffee)
self.amount = amount
def cost(self):
return super().cost() + 0.2 * self.amount
def description(self):
return super().description() + f", Sugar({self.amount})"
# Driver code
if __name__ == "__main__":
coffee = SimpleCoffee()
coffee = MilkDecorator(coffee, amount=2)
coffee = SugarDecorator(coffee, amount=3)
print(coffee.description()) # Coffee, Milk(2), Sugar(3)
print(coffee.cost()) # 5 + 1.0 + 0.6 = 6.6
Line Notes
def __init__(self, coffee, amount=1):Decorator accepts parameter to customize behavior dynamically
self.amount = amountStores amount to use in cost and description calculations
return super().cost() + 0.5 * self.amountCost scales with amount of milk added
return super().description() + f", Milk({self.amount})"Description shows amount dynamically for clarity
interface Coffee {
double cost();
String description();
}
class SimpleCoffee implements Coffee {
public double cost() { return 5; }
public String description() { return "Coffee"; }
}
abstract class CoffeeDecorator implements Coffee {
protected Coffee coffee;
public CoffeeDecorator(Coffee coffee) {
this.coffee = coffee;
}
public double cost() { return coffee.cost(); }
public String description() { return coffee.description(); }
}
class MilkDecorator extends CoffeeDecorator {
private int amount;
public MilkDecorator(Coffee coffee, int amount) {
super(coffee);
this.amount = amount;
}
public double cost() { return super.cost() + 0.5 * amount; }
public String description() { return super.description() + ", Milk(" + amount + ")"; }
}
class SugarDecorator extends CoffeeDecorator {
private int amount;
public SugarDecorator(Coffee coffee, int amount) {
super(coffee);
this.amount = amount;
}
public double cost() { return super.cost() + 0.2 * amount; }
public String description() { return super.description() + ", Sugar(" + amount + ")"; }
}
public class Main {
public static void main(String[] args) {
Coffee coffee = new SimpleCoffee();
coffee = new MilkDecorator(coffee, 2);
coffee = new SugarDecorator(coffee, 3);
System.out.println(coffee.description()); // Coffee, Milk(2), Sugar(3)
System.out.println(coffee.cost()); // 6.6
}
}
Line Notes
private int amount;Stores amount parameter for dynamic behavior customization
public MilkDecorator(Coffee coffee, int amount) {Constructor accepts amount to customize milk addition
return super.cost() + 0.5 * amount;Cost scales with amount of milk added
return super.description() + ", Milk(" + amount + ")";Description shows amount dynamically for clarity
#include <iostream>
#include <string>
#include <memory>
class Coffee {
public:
virtual double cost() = 0;
virtual std::string description() = 0;
virtual ~Coffee() {}
};
class SimpleCoffee : public Coffee {
public:
double cost() override { return 5; }
std::string description() override { return "Coffee"; }
};
class CoffeeDecorator : public Coffee {
protected:
std::shared_ptr<Coffee> coffee;
public:
CoffeeDecorator(std::shared_ptr<Coffee> c) : coffee(c) {}
double cost() override { return coffee->cost(); }
std::string description() override { return coffee->description(); }
};
class MilkDecorator : public CoffeeDecorator {
int amount;
public:
MilkDecorator(std::shared_ptr<Coffee> c, int amt) : CoffeeDecorator(c), amount(amt) {}
double cost() override { return CoffeeDecorator::cost() + 0.5 * amount; }
std::string description() override { return CoffeeDecorator::description() + ", Milk(" + std::to_string(amount) + ")"; }
};
class SugarDecorator : public CoffeeDecorator {
int amount;
public:
SugarDecorator(std::shared_ptr<Coffee> c, int amt) : CoffeeDecorator(c), amount(amt) {}
double cost() override { return CoffeeDecorator::cost() + 0.2 * amount; }
std::string description() override { return CoffeeDecorator::description() + ", Sugar(" + std::to_string(amount) + ")"; }
};
int main() {
std::shared_ptr<Coffee> coffee = std::make_shared<SimpleCoffee>();
coffee = std::make_shared<MilkDecorator>(coffee, 2);
coffee = std::make_shared<SugarDecorator>(coffee, 3);
std::cout << coffee->description() << std::endl; // Coffee, Milk(2), Sugar(3)
std::cout << coffee->cost() << std::endl; // 6.6
return 0;
}
Line Notes
int amount;Stores amount parameter for dynamic behavior customization
MilkDecorator(std::shared_ptr<Coffee> c, int amt) : CoffeeDecorator(c), amount(amt) {}Constructor accepts amount to customize milk addition
double cost() override { return CoffeeDecorator::cost() + 0.5 * amount; }Cost scales with amount of milk added
std::string description() override { return CoffeeDecorator::description() + ", Milk(" + std::to_string(amount) + ")"; }Description shows amount dynamically for clarity
class Coffee {
cost() {
throw new Error("Method not implemented");
}
description() {
throw new Error("Method not implemented");
}
}
class SimpleCoffee extends Coffee {
cost() {
return 5;
}
description() {
return "Coffee";
}
}
class CoffeeDecorator extends Coffee {
constructor(coffee) {
super();
this.coffee = coffee;
}
cost() {
return this.coffee.cost();
}
description() {
return this.coffee.description();
}
}
class MilkDecorator extends CoffeeDecorator {
constructor(coffee, amount = 1) {
super(coffee);
this.amount = amount;
}
cost() {
return super.cost() + 0.5 * this.amount;
}
description() {
return super.description() + `, Milk(${this.amount})`;
}
}
class SugarDecorator extends CoffeeDecorator {
constructor(coffee, amount = 1) {
super(coffee);
this.amount = amount;
}
cost() {
return super.cost() + 0.2 * this.amount;
}
description() {
return super.description() + `, Sugar(${this.amount})`;
}
}
// Driver code
const coffee = new SugarDecorator(new MilkDecorator(new SimpleCoffee(), 2), 3);
console.log(coffee.description()); // Coffee, Milk(2), Sugar(3)
console.log(coffee.cost()); // 6.6
Line Notes
constructor(coffee, amount = 1) {Decorator accepts amount parameter to customize behavior dynamically
this.amount = amount;Stores amount for use in cost and description calculations
return super.cost() + 0.5 * this.amount;Cost scales with amount of milk added
return super.description() + `, Milk(${this.amount})`;Description shows amount dynamically for clarity
TimeO(k) for k decorators stacked
SpaceO(k) for k decorator objects
Similar to previous approach but with added flexibility from parameters.
💡 Allows dynamic customization without subclassing or code duplication.
Interview Verdict: Accepted and highly flexible
This approach is best for real-world scenarios requiring dynamic behavior customization.