Decorator Pattern
Part of the Object Oriented Programming section of Coddy's Java journey — lesson 72 of 87.
The Decorator Pattern is a structural design pattern that lets you attach new behaviors to objects by wrapping them in special objects called decorators. Unlike inheritance, which adds behavior at compile time, decorators add functionality dynamically at runtime without modifying the original class.
Imagine a coffee shop where you start with a basic coffee and can add extras like milk, sugar, or whipped cream. Each addition wraps the original coffee, adding its cost and description. The pattern uses a common interface so decorated objects can be treated the same as the original:
interface Coffee {
String getDescription();
double getCost();
}
class SimpleCoffee implements Coffee {
public String getDescription() {
return "Simple Coffee";
}
public double getCost() {
return 2.0;
}
}
abstract class CoffeeDecorator implements Coffee {
protected Coffee coffee;
public CoffeeDecorator(Coffee coffee) {
this.coffee = coffee;
}
}
class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
public String getDescription() {
return coffee.getDescription() + ", Milk";
}
public double getCost() {
return coffee.getCost() + 0.5;
}
}The decorator holds a reference to the wrapped object and delegates calls to it while adding its own behavior. You can stack multiple decorators to combine features:
Coffee order = new SimpleCoffee();
order = new MilkDecorator(order);
order = new SugarDecorator(order);
System.out.println(order.getDescription()); // Simple Coffee, Milk, Sugar
System.out.println(order.getCost()); // 2.75The Decorator Pattern shines when you need to add responsibilities to objects without creating an explosion of subclasses. Each decorator is independent, so you can mix and match them freely to create exactly the combination of behaviors you need.
Challenge
EasyLet's build a notification system using the Decorator Pattern! You'll create a base notification that can be enhanced with different delivery channels—email alerts, SMS alerts, and push notifications—by wrapping decorators around each other. This mirrors real-world systems where a single message might need to go out through multiple channels simultaneously.
You'll organize your code across four files:
Notifier.java: Define theNotifierinterface that serves as the component interface for your decorator pattern. It should declare a methodsend(String message)that handles sending notifications.BasicNotifier.java: Create the concrete component class that implementsNotifier. This represents the simplest form of notification—an in-app alert. Whensendis called, it should printIn-App: [message].NotifierDecorators.java: Build your decorator hierarchy here. Start with an abstractNotifierDecoratorclass that implementsNotifierand holds a reference to a wrappedNotifierobject (passed through its constructor).Then create three concrete decorators that extend
NotifierDecorator:EmailDecorator- Whensendis called, it first delegates to the wrapped notifier, then printsEmail: [message].SMSDecorator- Whensendis called, it first delegates to the wrapped notifier, then printsSMS: [message].PushDecorator- Whensendis called, it first delegates to the wrapped notifier, then printsPush: [message].Main.java: Bring your decorator system together! You'll receive two inputs: the notification message (String) and a comma-separated list of channels to add (String), for example"email,sms"or"push,email,sms".Start with a
BasicNotifier. Then, based on the channels input, wrap it with the appropriate decorators in the order they appear. Valid channel names areemail,sms, andpush.Finally, call
sendwith your message on the fully decorated notifier.
You will receive two inputs in order: the message to send (String) and the channels to enable (String, comma-separated).
For example, with inputs Server is down! and email,push, your output would be:
In-App: Server is down!
Email: Server is down!
Push: Server is down!Notice how each decorator adds its own behavior while preserving the original notification. The order of wrapping determines the order of output—the innermost component (BasicNotifier) executes first, then each decorator adds its layer. You can mix and match any combination of channels without creating separate classes for each combination!
Cheat sheet
The Decorator Pattern is a structural design pattern that attaches new behaviors to objects by wrapping them in decorator objects. It adds functionality dynamically at runtime without modifying the original class.
The pattern uses a common interface so decorated objects can be treated the same as the original object.
Basic Structure
Define a component interface:
interface Coffee {
String getDescription();
double getCost();
}Create a concrete component that implements the interface:
class SimpleCoffee implements Coffee {
public String getDescription() {
return "Simple Coffee";
}
public double getCost() {
return 2.0;
}
}Create an abstract decorator class that implements the same interface and holds a reference to the wrapped object:
abstract class CoffeeDecorator implements Coffee {
protected Coffee coffee;
public CoffeeDecorator(Coffee coffee) {
this.coffee = coffee;
}
}Create concrete decorators that extend the abstract decorator:
class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
public String getDescription() {
return coffee.getDescription() + ", Milk";
}
public double getCost() {
return coffee.getCost() + 0.5;
}
}Using Decorators
Stack multiple decorators to combine features:
Coffee order = new SimpleCoffee();
order = new MilkDecorator(order);
order = new SugarDecorator(order);
System.out.println(order.getDescription()); // Simple Coffee, Milk, Sugar
System.out.println(order.getCost()); // 2.75The decorator delegates calls to the wrapped object while adding its own behavior. Each decorator is independent and can be mixed and matched freely.
Try it yourself
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// Read the message to send
String message = scanner.nextLine();
// Read the comma-separated list of channels (e.g., "email,sms" or "push,email,sms")
String channelsInput = scanner.nextLine();
// TODO: Start with a BasicNotifier
// TODO: Split the channels input by comma
// TODO: Loop through each channel and wrap the notifier with the appropriate decorator
// Valid channels are: "email", "sms", "push"
// TODO: Call send on the fully decorated notifier with the message
}
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of OOP
External FilesIntroduction to OOPClasses vs ObjectsThe this KeywordMethodsFields (Attributes)Constructor MethodConstructor OverloadingRecap - Simple Calculator4Inheritance
Basic Inheritance (extends)The super KeywordMethod Overriding (@Override)Constructor ChainingThe Object ClassSingle & Multilevel InheritWhy No Multi Class InheritRecap - Employee Hierarchy7Special Methods & Object Class
toString() Methodequals() and hashCode()clone() MethodcompareTo() and ComparableComparator InterfaceRecap - Custom Sorting2Access Modifiers & Encapsulate
Access Levels OverviewGetter and Setter MethodsInformation HidingThe final KeywordRecap - Bank Account Manager5Polymorphism
Method Overloading BasicsMethod Overriding (Run-Time)Upcasting and DowncastingThe instanceof OperatorAbstract Classes and MethodsRecap - Shape Calculator8Advanced OOP Concepts
Composition vs InheritanceAggregation vs CompositionInner Nested & Anonymous ClassEnums and Enum MethodsRecords (Java 16+)Sealed Classes (Java 17+)3Class Props & Static Member
Instance vs Static VariablesStatic MethodsStatic BlocksConstants (static final)Recap - Counter & Utility6Interfaces & Abstract Classes
Introduction to InterfacesImplementing InterfacesMulti Interface ImplemenDefault & Static in InterfaceAbstract Classes vs InterfacesFunctional InterfacesRecap - Payment System9Generics
Introduction to GenericsGeneric ClassesGeneric MethodsBounded Type ParametersWildcards (?, extends, super)Recap - Generic Container12Design Patterns Part 2
Command PatternAdapter PatternDecorator PatternTemplate Method PatternState PatternComposite PatternIterator Pattern