A widely used real-world example of the Decorator pattern is a coffee ordering system, where a base coffee object is wrapped by one or more decorators representing add-ons, each contributing its own cost and description.
Key Points: • A base Coffee interface defines cost() and description(), implemented by a SimpleCoffee class. • Each add-on (milk, sugar, whipped cream) is a decorator class wrapping a Coffee reference. • Every decorator's cost() adds its own price to the wrapped object's cost(), building up the total incrementally. • Decorators can be combined in any order and quantity, e.g. double milk plus sugar, without new subclasses. • This mirrors Java's own I/O classes, where BufferedReader wraps a Reader the same way.
Example: Wrapping new SugarDecorator(new MilkDecorator(new SimpleCoffee())) produces a coffee with milk and sugar added, and calling cost() on the outermost wrapper sums the base price plus both add-ons.
Code Example:
interface Coffee {
double cost();
String description();
}
class SimpleCoffee implements Coffee {
public double cost() { return 2.0; }
public String description() { return "Coffee"; }
}
class MilkDecorator implements Coffee {
private Coffee coffee;
MilkDecorator(Coffee coffee) { this.coffee = coffee; }
public double cost() { return coffee.cost() + 0.5; }
public String description() { return coffee.description() + " + Milk"; }
}Interview Tip: A concise interview answer is:
"The go-to real-world example is a coffee shop ordering system: you start with a plain coffee object and wrap it with decorators like milk or sugar, each adding its own cost and description. It avoids needing a separate subclass for every possible combination of add-ons, and it's the same idea Java uses for its I/O stream classes."