Decorator Design Pattern in Java: Add Features Without Changing Existing Code
Introduction
When developing Java applications, you may need to add new features to an existing object without modifying its source code. One way to achieve this is by using the Decorator Design Pattern.
The Decorator Pattern is a structural design pattern that allows behavior to be added to an object dynamically by wrapping it inside another object. It follows the Open/Closed Principle, which states that software entities should be open for extension but closed for modification.
Why Use the Decorator Pattern?
Imagine you have a coffee ordering system.
A customer may order:
- Simple Coffee
- Coffee with Milk
- Coffee with Sugar
- Coffee with Milk and Sugar
Creating separate classes for every combination would lead to a large number of subclasses. Instead, the Decorator Pattern allows us to add these features dynamically without creating new subclasses.
Structure of the Decorator Pattern
The pattern consists of four main components:
1. Component
Defines the common interface for all objects.
2. Concrete Component
Provides the basic implementation.
3. Decorator
Implements the component interface and stores a reference to a component object.
4. Concrete Decorator
Adds new functionality before or after calling the original object's methods.
Class Diagram
Component
▲
│
ConcreteComponent
▲
│
Decorator
▲
│
ConcreteDecorator
Java Implementation
Step 1: Create the Component Interface
interface Coffee {
String getDescription();
double getCost();
}
Step 2: Create the Concrete Component
class SimpleCoffee implements Coffee {
@Override
public String getDescription() {
return "Simple Coffee";
}
@Override
public double getCost() {
return 50.0;
}
}
Step 3: Create the 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();
}
}
Step 4: Create a Concrete Decorator
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() + 20;
}
}
Step 5: Test the Pattern
public class DecoratorDemo {
public static void main(String[] args) {
Coffee coffee = new SimpleCoffee();
System.out.println(coffee.getDescription());
System.out.println("Cost: ₹" + coffee.getCost());
coffee = new MilkDecorator(coffee);
System.out.println(coffee.getDescription());
System.out.println("Cost: ₹" + coffee.getCost());
}
}
Output
Simple Coffee
Cost: ₹50.0
Simple Coffee + Milk
Cost: ₹70.0
How It Works
- A
SimpleCoffeeobject is created. - The object provides the basic description and cost.
- A
MilkDecoratorwraps the original coffee object. - The decorator adds extra behavior by updating both the description and the cost.
- The original class remains unchanged while new functionality is added dynamically.
Advantages
- Follows the Open/Closed Principle.
- Adds functionality without modifying existing code.
- Eliminates the need for many subclasses.
- Promotes reusable and maintainable code.
- Features can be combined dynamically.
Disadvantages
- Introduces additional classes.
- Multiple decorators may make debugging harder.
- The object structure becomes more complex as decorators increase.
Real-World Applications
The Decorator Pattern is commonly used in:
- Coffee ordering systems
- Pizza customization applications
- Java Input/Output Streams
- GUI frameworks
- Notification systems
- Logging frameworks
A well-known Java example is the java.io package, where classes like BufferedInputStream and DataInputStream decorate an existing InputStream by adding extra functionality.
When Should You Use It?
Use the Decorator Pattern when:
- You need to add features at runtime.
- You want to avoid creating many subclasses.
- You want to extend functionality without modifying existing classes.
- Multiple combinations of features are possible.
Conclusion
The Decorator Design Pattern is one of the most useful structural design patterns in Java. It provides a clean and flexible way to extend the behavior of objects without changing their original implementation. By wrapping objects instead of modifying them, developers can build applications that are easier to maintain, extend, and reuse.
If your application requires adding optional features dynamically, the Decorator Pattern is an excellent choice.
Top comments (0)