1. Definition
The Decorator Design Pattern attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.
2. Problem Statement
Imagine you are developing a system for a coffee shop and you want to offer a variety of beverages, like espresso, dark roast, etc. On top of that, customers can customize their beverages with several condiments such as milk, soy, mocha, and whipped cream. How can you design your classes to support this dynamic behavior without creating a large number of subclasses for every possible combination?
3. Solution
The Decorator Pattern provides an elegant solution by allowing you to wrap objects with objects that augment their behavior. This wrapping can be done multiple times to achieve the desired combination of behaviors.
4. Real-World Use Cases
1. Graphical User Interface (GUI) toolkits use decorators to add behaviors to visual components.
2. Java’s I/O classes, like BufferedInputStream and LineNumberInputStream.
3. Middleware systems that add various responsibilities dynamically.
5. Implementation Steps
1. Define a component interface for the objects to which you want to add responsibilities dynamically.
2. Create concrete components that implement the component interface.
3. Create abstract decorator classes that also implement the component interface.
4. Define concrete decorators for the functionalities you want to add, inheriting from the abstract decorator.
5. Client can then compose the desired functionalities by wrapping the objects.
6. Implementation
// Step 1: Component Interface
public interface Beverage {
String getDescription();
double cost();
}
// Step 2: Concrete Components
public class Espresso implements Beverage {
public String getDescription() {
return "Espresso";
}
public double cost() {
return 1.99;
}
}
public class DarkRoast implements Beverage {
public String getDescription() {
return "Dark Roast";
}
public double cost() {
return 0.99;
}
}
// Step 3: Abstract Decorator
public abstract class CondimentDecorator implements Beverage {
protected Beverage beverage;
}
// Step 4: Concrete Decorators
public class Milk extends CondimentDecorator {
public Milk(Beverage beverage) {
this.beverage = beverage;
}
public String getDescription() {
return beverage.getDescription() + ", Milk";
}
public double cost() {
return beverage.cost() + 0.20;
}
}
public class Mocha extends CondimentDecorator {
public Mocha(Beverage beverage) {
this.beverage = beverage;
}
public String getDescription() {
return beverage.getDescription() + ", Mocha";
}
public double cost() {
return beverage.cost() + 0.50;
}
}
// Client Code
public class CoffeeShop {
public static void main(String[] args) {
Beverage beverage = new Espresso();
System.out.println(beverage.getDescription() + " $" + beverage.cost());
Beverage beverage2 = new DarkRoast();
beverage2 = new Milk(beverage2);
beverage2 = new Mocha(beverage2);
System.out.println(beverage2.getDescription() + " $" + beverage2.cost());
}
}
Output:
Espresso $1.99 Dark Roast, Milk, Mocha $1.69
Explanation:
In our example, the Espresso and DarkRoast are concrete beverages. We can dynamically add functionalities to these beverages by wrapping them in decorators like Milk and Mocha. This allows for a flexible way to combine behaviors without a proliferation of subclasses.
7. When to use?
1. Use the Decorator Pattern when you want to add responsibilities to objects dynamically and transparently, without affecting other objects.
2. When extension by subclassing is impractical due to a proliferation of subclasses.