Understanding the Decorator Pattern Through a Coffee Pricing System

The Decorator Pattern is a structural design pattern that allows behavior to be added to individual objects, either statically or dynamical, without afffecting the behavior of other objects from the same class. It emphasizes composition over inheritance and adheres to the Open/Closed Principle: open for extension, but closed for modification. A classic real-world implementation can be found in Java I/O streams.

Class Diagram:

Decorator Pattern Class Diagram

The following example, inspired by Head First Design Patterns, simulates a beverage shop where different drinks can be customized with condiments like Mocha, Soy, and Whip. Each add-on decorates the base beverage, stacking behaviors and costs dynamically.

1. Defining the Abstract Beverage

Every beverage must provide a description and a cost.

package design.decorator;

public abstract class Drink {
    protected String label = "Unnamed Drink";

    public String getLabel() {
        return label;
    }

    public abstract double calculatePrice();
}

2. Implementing Concrete Beverages

Different drink types with fixed base prices.

package design.decorator.beverages;

import design.decorator.Drink;

public class ShortBlack extends Drink {

    public ShortBlack() {
        label = "Short Black";
    }

    public double calculatePrice() {
        return 2.49;
    }
}
package design.decorator.beverages;

import design.decorator.Drink;

public class Latte extends Drink {

    public Latte() {
        label = "Café Latte";
    }

    public double calculatePrice() {
        return 3.19;
    }
}
package design.decorator.beverages;

import design.decorator.Drink;

public class ColdBrew extends Drink {

    public ColdBrew() {
        label = "Cold Brew Coffee";
    }

    public double calculatePrice() {
        return 2.99;
    }
}

3. Abstract Decorator for Extras

An abstract class that wraps a Drink object and ensures extended description reporting.

package design.decorator;

public abstract class DrinkExtra extends Drink {
    public abstract String getLabel();
}

4. Concrete Decorators (Extras)

Each extra decorates the wrapped drink by appending its own description and price.

package design.decorator.extras;

import design.decorator.Drink;
import design.decorator.DrinkExtra;

public class Chocolate extends DrinkExtra {
    Drink coreDrink;

    public Chocolate(Drink core) {
        this.coreDrink = core;
    }

    public String getLabel() {
        return coreDrink.getLabel() + ", Choc Shots";
    }

    public double calculatePrice() {
        return 0.55 + coreDrink.calculatePrice();
    }
}
package design.decorator.extras;

import design.decorator.Drink;
import design.decorator.DrinkExtra;

public class AlmondMilk extends DrinkExtra {
    Drink coreDrink;

    public AlmondMilk(Drink core) {
        this.coreDrink = core;
    }

    public String getLabel() {
        return coreDrink.getLabel() + ", Almond Milk";
    }

    public double calculatePrice() {
        return 0.65 + coreDrink.calculatePrice();
    }
}
package design.decorator.extras;

import design.decorator.Drink;
import design.decorator.DrinkExtra;

public class VanillaFoam extends DrinkExtra {
    Drink coreDrink;

    public VanillaFoam(Drink core) {
        this.coreDrink = core;
    }

    public String getLabel() {
        return coreDrink.getLabel() + ", Vanilla Foam";
    }

    public double calculatePrice() {
        return 0.45 + coreDrink.calculatePrice();
    }
}

5. Composing and Running the Order

Clients can stack multiple decorators around a base drink. Each layer adds its own cost and description segment.

package design.decorator;

import design.decorator.beverages.ColdBrew;
import design.decorator.beverages.Latte;
import design.decorator.beverages.ShortBlack;
import design.decorator.extras.AlmondMilk;
import design.decorator.extras.Chocolate;
import design.decorator.extras.VanillaFoam;

public class CoffeeShop {

    public static void main(String[] args) {
        Drink order1 = new ShortBlack();
        System.out.println(order1.getLabel() + " $" + order1.calculatePrice());

        Drink order2 = new Latte();
        order2 = new Chocolate(order2);
        order2 = new Chocolate(order2);
        order2 = new VanillaFoam(order2);
        System.out.println(order2.getLabel() + " $" + order2.calculatePrice());

        Drink order3 = new ColdBrew();
        order3 = new AlmondMilk(order3);
        order3 = new Chocolate(order3);
        order3 = new VanillaFoam(order3);
        System.out.println(order3.getLabel() + " $" + order3.calculatePrice());
    }
}

When you run this application, each drink prints its full description and the dynamically calculated total cost, demonstrating how the Decorator Pattern cleanly composes behavior at runtime.

Tags: Design Patterns Decorator Pattern java Object-Oriented Programming composition

Posted on Sun, 18 Oct 2026 16:29:03 +0000 by melkara