Coffee Shop System - Decorator Pattern Implementation
This is an implementation of the Decorator design pattern for a coffee shop billing system where customers can dynamically add various toppings to their base coffee order.
Problem Statement
Building billing software for a coffee shop (like Starbucks) where customers can customize their coffee with various add-ons. The challenge is to handle dynamic combinations of add-ons without creating separate classes for every possible combination (Coffee, CoffeeWithMilk, CoffeeWithSugar, CoffeeWithMilkAndSugar, etc.).
Key Challenge: A customer can order:
- Just Coffee
- Coffee + Milk
- Coffee + Milk + Sugar
- Coffee + Milk + Milk (Double Milk!)
- Any combination of add-ons in any order
Class Diagram
+----------------+
| Coffee | <-------------------------+
| (Interface) | |
+----------------+ |
| + getCost() | |
| + getDesc() | |
+-------+--------+ |
^ |
| (Implements) |
| | (Wraps / Has-A)
+----------+-----------+ +----------+----------+
| | | |
+-----+--------+ +--------+-------------+---+ +----------+----------+
| SimpleCoffee | | CoffeeDecorator |<>----| coffee field |
+--------------+ | (Abstract) | +---------------------+
| Returns 10.0 | +--------------------------+
+--------------+ ^
| (Extends)
|
+--------------+--------------+
| |
+--------+--------+ +--------+--------+
| MilkDecorator | | SugarDecorator |
+-----------------+ +-----------------+
| + getCost() | | + getCost() |
| return 2.0 | | return 5.0 |
| + super.cost | | + super.cost |
+-----------------+ +-----------------+
Implementation
package decorator.coffeeshopsystem;
public class CoffeeShopSystem {
/**
* Component Interface.
* Defines the blueprint for both the base object and the decorators.
*/
interface Coffee {
String getDescription();
double getCost();
}
/**
* Concrete Component.
* The base object (plain coffee) that we will decorate.
*/
static class SimpleCoffee implements Coffee {
@Override
public String getDescription() {
return "Simple Coffee";
}
@Override
public double getCost() {
return 10.0;
}
}
/**
* Abstract Decorator.
* Implements the interface AND holds a reference to a Component (HAS-A relationship).
*/
static abstract class CoffeeDecorator implements Coffee {
protected Coffee coffee; // The object being wrapped
public CoffeeDecorator(Coffee coffee) {
this.coffee = coffee;
}
@Override
public String getDescription() {
return coffee.getDescription(); // Delegate to the wrapped object
}
@Override
public double getCost() {
return coffee.getCost(); // Delegate to the wrapped object
}
}
/**
* Concrete Decorator (Milk).
* Adds functionality (cost and description) to the wrapped object.
*/
static class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return super.getDescription() + ", Milk";
}
@Override
public double getCost() {
return super.getCost() + 2.0; // Add cost of Milk
}
}
/**
* Concrete Decorator (Sugar).
* Adds functionality (cost and description) to the wrapped object.
*/
static class SugarDecorator extends CoffeeDecorator {
public SugarDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return super.getDescription() + ", Sugar";
}
@Override
public double getCost() {
return super.getCost() + 5.0; // Add cost of Sugar
}
}
public static void main(String[] args) {
System.out.println("---- Coffee Shop Order ----");
// 1. Order Base Coffee
Coffee myCoffee = new SimpleCoffee();
System.out.println(myCoffee.getDescription() + " : $" + myCoffee.getCost());
// 2. Decorate with Milk
myCoffee = new MilkDecorator(myCoffee);
System.out.println(myCoffee.getDescription() + " : $" + myCoffee.getCost());
// 3. Decorate with Sugar
myCoffee = new SugarDecorator(myCoffee);
System.out.println(myCoffee.getDescription() + " : $" + myCoffee.getCost());
}
}
Key Features
- Decorator Pattern: Dynamically adds responsibilities to objects without modifying their classes
- Flexible Combinations: Supports any combination of add-ons in any order
- Open/Closed Principle: Open for extension (new decorators), closed for modification
- Single Responsibility: Each decorator has one specific responsibility
- Composition over Inheritance: Uses wrapping instead of subclassing
- Stackable Decorators: Can apply multiple decorators to the same object
How It Works
- Component Interface (Coffee): Defines the common interface for both base objects and decorators
- Concrete Component (SimpleCoffee): The basic coffee without any add-ons
- Abstract Decorator (CoffeeDecorator): Implements the Coffee interface and holds a reference to a Coffee object
- Concrete Decorators (MilkDecorator, SugarDecorator): Add specific functionality by wrapping the coffee object
- Wrapping Process: Each decorator wraps the previous object, creating a chain of decorators
Sample Output
---- Coffee Shop Order ----
Simple Coffee : $10.0
Simple Coffee, Milk : $12.0
Simple Coffee, Milk, Sugar : $17.0
Visualization of Wrapping
Order: Coffee + Milk + Sugar
SugarDecorator
└── wraps MilkDecorator
└── wraps SimpleCoffee
When getCost() is called:
1. SugarDecorator.getCost() = super.getCost() + 5.0
2. MilkDecorator.getCost() = super.getCost() + 2.0
3. SimpleCoffee.getCost() = 10.0
4. Result: 10.0 + 2.0 + 5.0 = 17.0
Real-World Applications
- Coffee shop ordering systems (Starbucks, Dunkin')
- Pizza topping customization
- Car configuration with optional features
- Insurance policy add-ons
- Software licensing with feature modules
- Stream/IO operations in Java (BufferedReader wrapping FileReader)
- UI component decoration (borders, scrollbars)
Advantages Over Inheritance
Without Decorator (Using Inheritance):
- CoffeeWithMilk
- CoffeeWithSugar
- CoffeeWithMilkAndSugar
- CoffeeWithDoubleMilk
- CoffeeWithDoubleMilkAndSugar
- ... (Class explosion!)
With Decorator:
- SimpleCoffee
- MilkDecorator
- SugarDecorator
- Can create any combination dynamically!
Adding New Decorators
Adding a new add-on is simple:
static class WhippedCreamDecorator extends CoffeeDecorator {
public WhippedCreamDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return super.getDescription() + ", Whipped Cream";
}
@Override
public double getCost() {
return super.getCost() + 3.0;
}
}
No need to modify existing code - just add the new decorator class!
Top comments (0)