Junior
What is a decorator in programming?
sobes.tech AI
Answer from AI
Decorator is a structural design pattern that allows adding new behavior to objects dynamically by wrapping them in special decorator objects.
Core principles:
- Decorators have the same interface as the object they decorate.
- Decorator contains a reference to the object being decorated.
- Requests to the decorator are forwarded to the decorated object, with the decorator able to perform additional actions before or after the original method call.
Advantages:
- Flexibility: we can combine different decorators to achieve the desired behavior.
- Avoids class hierarchy bloating, as with inheritance.
- Behavior can be added at runtime.
Disadvantages:
- Increased code structure complexity when using many decorators.
- It can be difficult to remove a specific decorator from the stack.
Example (Java):
Suppose we have an interface Coffee and its implementation SimpleCoffee. We want to add milk and sugar to the coffee. Instead of creating classes like MilkCoffee, SugarCoffee, MilkSugarCoffee, we use decorators.
// Interface
interface Coffee {
String getDescription();
double getCost();
}
// Concrete component (decorated object)
class SimpleCoffee implements Coffee {
@Override
public String getDescription() {
return "Simple coffee";
}
@Override
public double getCost() {
return 1.0;
}
}
// Base decorator class
abstract class CoffeeDecorator implements Coffee {
protected Coffee decoratedCoffee;
public CoffeeDecorator(Coffee coffee) {
this.decoratedCoffee = coffee;
}
@Override
public String getDescription() {
return decoratedCoffee.getDescription();
}
@Override
public double getCost() {
return decoratedCoffee.getCost();
}
}
// Concrete decorator: milk
class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return super.getDescription() + ", with milk";
}
@Override
public double getCost() {
return super.getCost() + 0.5;
}
}
// Concrete decorator: sugar
class SugarDecorator extends CoffeeDecorator {
public SugarDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return super.getDescription() + ", with sugar";
}
@Override
public double getCost() {
return super.getCost() + 0.2;
}
}
// Usage
public class Example {
public static void main(String[] args) {
Coffee myCoffee = new SimpleCoffee();
System.out.println(myCoffee.getDescription() + " costs " + myCoffee.getCost()); // Simple coffee costs 1.0
myCoffee = new MilkDecorator(myCoffee);
System.out.println(myCoffee.getDescription() + " costs " + myCoffee.getCost()); // Simple coffee, with milk costs 1.5
myCoffee = new SugarDecorator(myCoffee);
System.out.println(myCoffee.getDescription() + " costs " + myCoffee.getCost()); // Simple coffee, with milk, with sugar costs 1.7
}
}
Comparison with inheritance:
| Feature | Inheritance | Decorator |
|---|---|---|
| Adding behavior | Static (at compile time) | Dynamic (at runtime) |
| Flexibility | Limited (hierarchy bloating issue) | High (through composition) |
| Reusability | Possible through inheritance | Easy (one decorator for different objects) |
Used when you need to add responsibilities to objects without changing their code and without creating many subclasses. A good example in Java itself is reading/writing data using stream decorators (e.g., BufferedReader, BufferedWriter wrap base streams).