Java > Design Patterns in Java > Structural Patterns > Decorator Pattern
Decorator Pattern: Coffee Example
The Decorator pattern allows behavior to be added to an individual object, either statically or dynamically, without affecting the behavior of other objects from the same class. This example demonstrates decorating a simple `Coffee` object with `Milk` and `Sugar` additions.
Core Component: Coffee
This interface defines the basic contract for all Coffee objects. It specifies that each Coffee object must have a description and a cost.
interface Coffee {
String getDescription();
double getCost();
}
Concrete Component: SimpleCoffee
This class implements the `Coffee` interface and provides a basic implementation of a simple coffee object. It returns a description of 'Simple Coffee' and a cost of 1.0.
class SimpleCoffee implements Coffee {
@Override
public String getDescription() {
return "Simple Coffee";
}
@Override
public double getCost() {
return 1.0;
}
}
Decorator: CoffeeDecorator
This abstract class implements the `Coffee` interface and acts as the base decorator. It holds a reference to the decorated `Coffee` object and delegates calls to the `getDescription()` and `getCost()` methods to the decorated object. This allows decorators to add additional behavior without modifying the original object.
abstract class CoffeeDecorator implements Coffee {
protected Coffee decoratedCoffee;
public CoffeeDecorator(Coffee decoratedCoffee) {
this.decoratedCoffee = decoratedCoffee;
}
@Override
public String getDescription() {
return decoratedCoffee.getDescription();
}
@Override
public double getCost() {
return decoratedCoffee.getCost();
}
}
Concrete Decorator: Milk
This class extends the `CoffeeDecorator` and adds milk to the coffee. It overrides the `getDescription()` and `getCost()` methods to add the description of 'with Milk' and a cost of 0.5 to the decorated coffee.
class Milk extends CoffeeDecorator {
public Milk(Coffee decoratedCoffee) {
super(decoratedCoffee);
}
@Override
public String getDescription() {
return decoratedCoffee.getDescription() + ", with Milk";
}
@Override
public double getCost() {
return decoratedCoffee.getCost() + 0.5;
}
}
Concrete Decorator: Sugar
This class extends the `CoffeeDecorator` and adds sugar to the coffee. It overrides the `getDescription()` and `getCost()` methods to add the description of 'with Sugar' and a cost of 0.2 to the decorated coffee.
class Sugar extends CoffeeDecorator {
public Sugar(Coffee decoratedCoffee) {
super(decoratedCoffee);
}
@Override
public String getDescription() {
return decoratedCoffee.getDescription() + ", with Sugar";
}
@Override
public double getCost() {
return decoratedCoffee.getCost() + 0.2;
}
}
Usage Example
This example shows how to use the decorator pattern to add milk and sugar to a simple coffee object. The output will be: Description: Simple Coffee Cost: 1.0 Description: Simple Coffee, with Milk Cost: 1.5 Description: Simple Coffee, with Milk, with Sugar Cost: 1.7
public class DecoratorExample {
public static void main(String[] args) {
Coffee coffee = new SimpleCoffee();
System.out.println("Description: " + coffee.getDescription());
System.out.println("Cost: " + coffee.getCost());
Coffee milkCoffee = new Milk(coffee);
System.out.println("Description: " + milkCoffee.getDescription());
System.out.println("Cost: " + milkCoffee.getCost());
Coffee sugarMilkCoffee = new Sugar(milkCoffee);
System.out.println("Description: " + sugarMilkCoffee.getDescription());
System.out.println("Cost: " + sugarMilkCoffee.getCost());
}
}
Concepts Behind the Snippet
The Decorator pattern follows the principles of object-oriented design such as Open/Closed principle (open for extension but closed for modification). It promotes composition over inheritance, allowing for more flexible and dynamic behavior modification at runtime.
Real-Life Use Case
A common use case is in GUI frameworks where you might decorate a basic window with scrollbars, borders, or other features without modifying the underlying window class. Another use case involves adding encryption or compression to a data stream.
Best Practices
Interview Tip
Be prepared to discuss the differences between the Decorator pattern and inheritance. Explain how Decorator promotes composition over inheritance and allows for dynamic addition of responsibilities.
When to Use Them
Use the Decorator pattern when you need to add responsibilities to individual objects dynamically and transparently, without affecting other objects. This is useful when you want to avoid creating a large number of subclasses with different combinations of responsibilities.
Memory Footprint
The Decorator pattern introduces a slight overhead in terms of memory because it creates additional objects (the decorators). However, this overhead is usually negligible compared to the benefits of flexibility and maintainability.
Alternatives
Pros
Cons
FAQ
-
What is the difference between Decorator and Adapter pattern?
The Decorator pattern adds responsibilities to an object, while the Adapter pattern changes the interface of an object to match what the client expects. -
Can I use multiple decorators on the same object?
Yes, you can chain multiple decorators together to add multiple responsibilities to an object. -
Is the order of decorators important?
Yes, the order of decorators can affect the final result, especially if the decorators modify the same properties or behaviors.