This recipe shows how to implement the Decorator design pattern. This design pattern can be used when you need to add additional functionality to an individual object as opposed to a class of objects. It is especially useful when dealing with requirements that involve the composition of objects from a large set of options. With the Decorator design pattern, these compositions can be done dynamically in real-time.
Assume a hypothetical cafe that sells coffee and tea. Various options can be added to each drink, such as sugar, milk, tapioca pearls, etc. As the number of options grow, the number of combinations of these add-ons will grow exponentially. If you were to design a system to represent all the possible drinks with add-on combinations, you may want to avoid using inheritance, as you may end up with too many subclasses to manage. Rather, you may want to consider using the Decorator design pattern which leverages composition and could significantly reduce the number of classes created.
We start by defining an interface for Drink. All concrete drinks (e.g. coffee, tea) will implement this interface.
interface Drink {
String getName();
double getPrice();
}
Link To: Java Source Code
We can then define the concrete drink classes as follows.
class Coffee implements Drink {
@Override
public String getName() {
return "Coffee (+$4)";
}
@Override
public double getPrice() {
return 4.0;
}
}
Link To: Java Source Code
class Tea implements Drink {
@Override
public String getName() {
return "Tea (+$3)";
}
@Override
public double getPrice() {
return 3.0;
}
}
Link To: Java Source Code
Next, we define an abstract class for add-on decorators. All concrete add-on decorators (e.g. sugar, milk, tapioca, etc.) will extend this abstract class and override the getName() and getPrice() methods.
abstract class AddOnDecorator implements Drink {
// reference to object that is being decorated
protected final Drink drink;
public AddOnDecorator(Drink drink) {
this.drink = drink;
}
}
Link To: Java Source Code
We can then define the concrete add-on decorator classes as follows.
public class Sugar extends AddOnDecorator {
public Sugar(Drink drink) {
super(drink);
}
@Override
public String getName() {
return drink.getName() + " with sugar (+$0.10)";
}
@Override
public double getPrice() {
return drink.getPrice() + 0.1;
}
}
Link To: Java Source Code
public class Milk extends AddOnDecorator {
public Milk(Drink drink) {
super(drink);
}
@Override
public String getName() {
return drink.getName() + " with milk (+$0.10)";
}
@Override
public double getPrice() {
return drink.getPrice() + 0.1;
}
}
Link To: Java Source Code
public class Tapioca extends AddOnDecorator {
public Tapioca(Drink drink) {
super(drink);
}
@Override
public String getName() {
return drink.getName() + " with tapioca pearls (+$0.50)";
}
@Override
public double getPrice() {
return drink.getPrice() + 0.5;
}
}
Link To: Java Source Code
To demonstrate how these classes work, we use the following driver class.
public class Cafe {
public static void main(String[] args) {
final NumberFormat formatter = NumberFormat.getCurrencyInstance(new Locale("en", "US"));
// plain coffee
Drink coffee = new Coffee();
System.out.println(coffee.getName() + ": " + formatter.format(coffee.getPrice()));
// coffee with milk
coffee = new Milk(coffee);
System.out.println(coffee.getName() + ": " + formatter.format(coffee.getPrice()));
// coffee with milk and sugar
coffee = new Sugar(coffee);
System.out.println(coffee.getName() + ": " + formatter.format(coffee.getPrice()));
// plain tea
Drink tea = new Tea();
System.out.println(tea.getName() + ": " + formatter.format(tea.getPrice()));
// tea with sugar
tea = new Sugar(tea);
System.out.println(tea.getName() + ": " + formatter.format(tea.getPrice()));
// tea with sugar and tapioca pearls
tea = new Tapioca(tea);
System.out.println(tea.getName() + ": " + formatter.format(tea.getPrice()));
}
}
Link To: Java Source Code
Coffee (+$4): $4.00
Coffee (+$4) with milk (+$0.10): $4.10
Coffee (+$4) with milk (+$0.10) with sugar (+$0.10): $4.20
Tea (+$3): $3.00
Tea (+$3) with sugar (+$0.10): $3.10
Tea (+$3) with sugar (+$0.10) with tapioca pearls (+$0.50): $3.60