Overview
The Decorator Design Pattern allows behavior to be added to an individual object, either statically or dynamically, without affecting the behavior of other objects from the same class. It is a structural design pattern that uses composition instead of inheritance.
Key Characteristics
- Enables adding responsibilities to objects at runtime.
- Follows the Open/Closed Principle by allowing new functionality without modifying existing code.
- Uses composition to wrap an object within another object.
Implementation
The following is an example of a Decorator implementation in Java:
// Component
interface Coffee {
String getDescription();
double getCost();
}
// Concrete Component
class BasicCoffee implements Coffee {
@Override
public String getDescription() {
return "Basic Coffee";
}
@Override
public double getCost() {
return 2.0;
}
}
// Decorator
abstract class CoffeeDecorator implements Coffee {
protected Coffee coffee;
public CoffeeDecorator(Coffee coffee) {
this.coffee = coffee;
}
@Override
public String getDescription() {
return coffee.getDescription();
}
@Override
public double getCost() {
return coffee.getCost();
}
}
// Concrete Decorators
class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return coffee.getDescription() + ", Milk";
}
@Override
public double getCost() {
return coffee.getCost() + 0.5;
}
}
class SugarDecorator extends CoffeeDecorator {
public SugarDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return coffee.getDescription() + ", Sugar";
}
@Override
public double getCost() {
return coffee.getCost() + 0.2;
}
}
// Demo
public class DecoratorDemo {
public static void main(String[] args) {
Coffee coffee = new BasicCoffee();
System.out.println(coffee.getDescription() + " -> $" + coffee.getCost());
coffee = new MilkDecorator(coffee);
System.out.println(coffee.getDescription() + " -> $" + coffee.getCost());
coffee = new SugarDecorator(coffee);
System.out.println(coffee.getDescription() + " -> $" + coffee.getCost());
}
}
When to Use
- When you need to add responsibilities to individual objects dynamically without affecting others.
- When using inheritance would result in a large number of subclasses to support every combination of behaviors.
Advantages
- Allows behavior to be added dynamically.
- Promotes flexibility and adheres to the Open/Closed Principle.
Disadvantages
- Can result in a complex structure with multiple small classes.
- May make debugging and maintenance more challenging.