The Decorator Pattern in Java is a structural design pattern that allows behavior to be added to individual objects, either statically or dynamically, without affecting the behavior of other objects from the same class. It’s useful for extending functionalities of objects in a flexible and reusable manner.
Important Components
- Component: An abstract class or interface defining the core functionality of objects.
- Concrete Component: A class implementing the Component interface, representing the base object to be decorated.
- Decorator: An abstract class or interface that implements the Component interface and holds a reference to a Component object, forwarding calls to it while adding extra behavior.
- Concrete Decorator: A class that extends the Decorator, adding specific functionality before or after delegating to the wrapped Component.
How It Works
- The Component interface defines the operations that can be performed.
- The Concrete Component provides the base implementation.
- The Decorator implements the same interface and wraps a Component, delegating operations to it.
- The Concrete Decorator adds new behavior (e.g., logging, validation) before or after calling the wrapped Component’s methods.
- Decorators can be stacked, allowing multiple behaviors to be added in a chain.
Example in Java
Let’s model a coffee shop where a base coffee (component) can be enhanced with add-ons like milk or sugar (decorators).
// Component Interface
interface Coffee {
String getDescription();
double getCost();
}
// Concrete Component
class SimpleCoffee implements Coffee {
@Override
public String getDescription() {
return "Simple Coffee";
}
@Override
public double getCost() {
return 2.00;
}
}
// Decorator
abstract class CoffeeDecorator implements Coffee {
protected Coffee coffee;
public CoffeeDecorator(Coffee coffee) {
this.coffee = coffee;
}
@Override
public String getDescription() {
return coffee.getDescription();
}
@Override
public double getCost() {
return coffee.getCost();
}
}
// Concrete Decorators
class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return coffee.getDescription() + ", Milk";
}
@Override
public double getCost() {
return coffee.getCost() + 0.50;
}
}
class SugarDecorator extends CoffeeDecorator {
public SugarDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return coffee.getDescription() + ", Sugar";
}
@Override
public double getCost() {
return coffee.getCost() + 0.20;
}
}
// Usage
public class Main {
public static void main(String[] args) {
// Base coffee
Coffee coffee = new SimpleCoffee();
System.out.println(coffee.getDescription() + " $" + coffee.getCost());
// Coffee with milk
Coffee milkCoffee = new MilkDecorator(coffee);
System.out.println(milkCoffee.getDescription() + " $" + milkCoffee.getCost());
// Coffee with milk and sugar
Coffee milkSugarCoffee = new SugarDecorator(new MilkDecorator(coffee));
System.out.println(milkSugarCoffee.getDescription() + " $" + milkSugarCoffee.getCost());
}
}
/*
Simple Coffee $2.0
Simple Coffee, Milk $2.5
Simple Coffee, Milk, Sugar $2.7
*/
Code language: PHP (php)
Use case and Implementation
Adding additional features to `Account` objects, such as overdraft protection or account alerts, without modifying the underlying account class.
//DecoratorPatternDemo.java interface Account { void deposit(double amount); void withdraw(double amount); double getBalance(); } class SavingsAccount implements Account { private double balance; public SavingsAccount(double initialBalance) { this.balance = initialBalance; } @Override public void deposit(double amount) { balance += amount; System.out.println(amount + " deposited. Current balance: " + balance); } @Override public void withdraw(double amount) { if (balance >= amount) { balance -= amount; System.out.println(amount + " withdrawn. Current balance: " + balance); } else { System.out.println("Insufficient funds."); } } @Override public double getBalance() { return balance; } } interface AccountDecorator extends Account { } class OverdraftAccount implements AccountDecorator { private Account account; private double overdraftLimit; public OverdraftAccount(Account account, double overdraftLimit) { this.account = account; this.overdraftLimit = overdraftLimit; } @Override public void deposit(double amount) { account.deposit(amount); } @Override public void withdraw(double amount) { if (account.getBalance() >= amount || (account.getBalance() - amount) >= -overdraftLimit) { account.withdraw(amount); } else { System.out.println("Overdraft limit exceeded."); } } @Override public double getBalance() { return account.getBalance(); } } class AlertAccount implements AccountDecorator { private Account account; public AlertAccount(Account account) { this.account = account; } @Override public void deposit(double amount) { account.deposit(amount); System.out.println("Alert: Deposit of " + amount + " made."); } @Override public void withdraw(double amount) { account.withdraw(amount); System.out.println("Alert: Withdrawal of " + amount + " made."); } @Override public double getBalance() { return account.getBalance(); } } public class DecoratorPatternDemo { public static void main(String[] args) { // Create a basic SavingsAccount Account basicAccount = new SavingsAccount(1000); // Add overdraft protection Account overdraftAccount = new OverdraftAccount(basicAccount, 500); // Add account alerts Account alertAccount = new AlertAccount(overdraftAccount); // Use the enhanced account alertAccount.deposit(200); alertAccount.withdraw(1500); alertAccount.withdraw(100); } } /* C:\>javac DecoratorPatternDemo.java C:\>java DecoratorPatternDemo 200.0 deposited. Current balance: 1200.0 Alert: Deposit of 200.0 made. Insufficient funds. Alert: Withdrawal of 1500.0 made. 100.0 withdrawn. Current balance: 1100.0 Alert: Withdrawal of 100.0 made. */
Pros
- Flexibility: Add or remove responsibilities dynamically by stacking decorators.
- Open/Closed Principle: Extends functionality without modifying existing code.
- Reusability: Decorators can be reused across different objects implementing the same interface.
- Single Responsibility: Each decorator focuses on a specific feature.
Cons
- Complexity: Multiple decorators can make the code harder to understand and debug.
- Object Proliferation: Stacking many decorators creates numerous small objects.
- Configuration Overhead: Managing decorator chains can be cumbersome.
When to Use
- When you need to add responsibilities to objects dynamically and transparently.
- When extending a class via inheritance is impractical (e.g., too many combinations).
- When you want to keep new functionality separate and reusable.
- When the system needs to support optional or combinable features.
Real-World Example
- Java I/O Streams: BufferedInputStream and DataInputStream are decorators that wrap an InputStream to add buffering or data parsing.
- GUI Frameworks: Adding borders, scrollbars, or tooltips to UI components dynamically.
- Web Middleware: Adding logging, authentication, or compression to HTTP request handlers.
The Decorator Pattern is a structural design pattern that allows behavior to be added to individual objects dynamically, without altering the original class structure. In Java, this pattern is ideal for enhancing the functionality of objects in a flexible and reusable manner—commonly used in input/output streams, GUI components, and feature-rich object models.