# Purpose
The Decorator pattern allows adding additional responsibilities to an object dynamically, at runtime, without altering the structure of existing classes.
It is especially useful for extending functionalities in a flexible and reusable way.
# Problem
The need to extend the functionality of a class dynamically, without resorting to inheritance, which generates a rigid and less flexible hierarchy.
# Solution
The solution proposed by the Decorator is to wrap the original object in a "decorator" object, which adds the desired functionality.
-
Object Decoration: Extends functionality by wrapping them with decorator classes.
-
Flexible Combination: Allows combining additional functionalities flexibly by using multiple decorators.
# Structure
# Participants
- Component: Interface for objects to which responsibilities are to be added.
- ConcreteComponent: Object to be decorated.
- Decorator: Abstract class that wraps a Component object.
- ConcreteDecorator: Adds functionality to the Component.
# When to Use It
-
Add responsibilities dynamically and transparently.
-
When extension by subclassing is impractical.
# Advantages
-
verified
More Flexibility: Dynamic responsibilities.
-
verified
Avoids overloaded classes: Separates optional functionalities.
-
verified
Combination and Reusability: Easy mixing of behaviors.
# Disadvantages
-
warning
Complexity: Multiple small layers.
-
warning
Configuration: Hard to configure with many layers.
-
warning
Identification: Hard to see the base object.
# Example: Customizable Coffee Shop
We are developing a system that needs to offer a large variety of customizable coffees (milk, sugar, cream, etc.).
Problem
Create a class for each combination would generate an explosion of classes hard to maintain.
Proposed solution
We use Decorator to dynamically add ingredients to the base coffee. Each decorator adds its own cost and description.
# Java Code
interface Coffee {
double getCost();
String getDescription();
}
class SimpleCoffee implements Coffee {
public double getCost() { return 2.0; }
public String getDescription() { return "Simple coffee"; }
}
abstract class CoffeeDecorator implements Coffee {
protected Coffee decoratedCoffee;
public CoffeeDecorator(Coffee coffee) { this.decoratedCoffee = coffee; }
public double getCost() { return decoratedCoffee.getCost(); }
public String getDescription() { return decoratedCoffee.getDescription(); }
}
class WithMilk extends CoffeeDecorator {
public WithMilk(Coffee coffee) { super(coffee); }
public double getCost() { return super.getCost() + 0.5; }
public String getDescription() { return super.getDescription() + ", with milk"; }
}
public class Client {
public static void main(String[] args) {
Coffee myCoffee = new SimpleCoffee();
myCoffee = new WithMilk(myCoffee);
myCoffee = new WithSugar(myCoffee);
System.out.println("Cost: " + myCoffee.getCost());
}
}
# Mapping Participants
- Coffee (Component): Common interface.
- SimpleCoffee (ConcreteComponent): Base component.
- CoffeeDecorator (Decorator): Base class for decorators.
- WithMilk, WithSugar (ConcreteDecorators): Functionality aggregators.
- Client: Assembles coffees with ingredients.
# Conclusions
The Decorator pattern gives a flexible and scalable solution, making it easy to add new ingredients in the future without modifying existing code.
# Related Patterns
Strategy
Decorator changes the skin; Strategy changes the guts of an object.
Composite
Decorator can be viewed as a degenerate Composite with only one component.
Adapter
Decorator changes responsibilities; Adapter changes the interface.