Decorator Pattern | Head First Design Pattern

Yan
3 min readJul 20, 2022

The third design pattern is the decorator pattern — open for extension but closed for modification. Imagine you are opening a coffee store, and we know there are some basic coffee type, like espresso and house blend coffee and so on. Customers will select one of them. However, for condiments like mocha, milk, soy or whip, customers have different choice combinations and hence different price.

Photo by Tyler Nix on Unsplash

One way to implement the code is to declare some boolean variables, like hasMilk, hasMocha… and when calculating cost, use some if else condition to add up all cost. However, as we have been stressed, we must consider future changes, like other condiments, or the change of price. Then such way is not optimal. Instead, we can use decorator pattern. The basic structure is like this:

One super class: Beverage

N+1 subclass: N contains espresso, houseblend and other base coffee. 1 means condiments.

N subclass of the 1: condiments have many subclasses, such as milk, soy…

Here is the code:

Beverage — note we have an abstract method cost() which will be implemented in its subclass.

public abstract class Beverage {
String description = "Unknown Beverage";

public String getDescription() {
return description;
}

public abstract double cost();
}

N base coffee, here are two:

public class Espresso extends Beverage{

public Espresso(){
description = "Espresso";
}

public double cost(){
return 1.99;
}
}
public class HouseBlend extends Beverage{
public HouseBlend(){
description = "House Blend Coffee";
}

public double cost(){
return 0.89;
}
}

1 condiment subclass: here condimentDecorator must extend Beverage, because its type must be Beverage. The reason is that every time we add a condiment, it becomes another beverage that can be decorated.

public abstract class CondimentDecorator extends  Beverage{
public abstract String getDescription();
}

Lastly, N subclass of the condiment: Mocha, Soy, Whip

//MOcha
public class Mocha extends CondimentDecorator{

Beverage beverage; // This will be the beverage that will be decorated, such as espresso

public Mocha(Beverage beverage){
this.beverage = beverage;
}

public String getDescription(){
return beverage.getDescription() + ", Mocha";
}

public double cost(){
return 0.20+beverage.cost();
}
}
//Soy
public class Soy extends CondimentDecorator{

Beverage beverage;

public Soy(Beverage beverage) {
this.beverage = beverage;
}

public String getDescription(){
return beverage.getDescription() +", Soy";
}


public double cost(){
return 0.10+beverage.cost();
}
}
}
// whip
public class Whip extends CondimentDecorator{

Beverage beverage;

public Whip(Beverage beverage) {
this.beverage = beverage;
}

public String getDescription(){
return beverage.getDescription() + ", whip";
}

public double cost(){
return beverage.cost()+1.1;
}
}

Now we can order some coffee!!!

The first coffee is just the base coffee without using decorator.

The second and third coffee is using decorator to add whatever you like. And each time we use previous beverage as the parameter, so that we can get information of description and cost and add them all up.

And! If you have more condiment, like coconut, you can just extend freely without changing Beverage code!

public class StarbuzzCoffee {

public static void main(String args[]){
Beverage beverage = new Espresso();
System.out.println(beverage.getDescription()+" $"+beverage.cost());

Beverage beverage2 = new DarkRoast();
beverage2 = new Mocha(beverage2);
beverage2 = new Mocha(beverage2);
beverage2 = new Whip(beverage2);
System.out.println(beverage2.getDescription()+" $"+beverage2.cost());

Beverage beverage3 = new HouseBlend();
beverage3 = new Soy(beverage3);
beverage3 = new Mocha(beverage3);
beverage3 = new Whip(beverage3);
System.out.println(beverage3.getDescription()+" $"+beverage3.cost());

}
}
// output
Espresso $1.99
Dark Roast, Mocha, Mocha, whip $2.7
House Blend Coffee, Soy, Mocha, whip $2.19

Coffee shop is a fabulous example to understand decorator pattern. One disadvantage is that it may contain many small classes (in this case, many condiments) and is confusing, but it will be less of a problem if you know the pattern (actually, some classes of JAVA I/O are also decorators). Tata!

--

--