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

  • Keep decorators simple and focused on a single responsibility.
  • Avoid creating too many decorators, as it can lead to a complex object structure.
  • Consider using a builder pattern in conjunction with the decorator pattern to simplify object creation.

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

  • Inheritance: Can lead to a class explosion if many combinations of responsibilities are needed.
  • Strategy Pattern: Useful when you want to encapsulate algorithms or behaviors and swap them at runtime.

Pros

  • Flexibility: Add responsibilities dynamically.
  • Open/Closed Principle: Open for extension, closed for modification.
  • Avoids Class Explosion: Prevents creating many subclasses.

Cons

  • Complexity: Can lead to a more complex object structure with many decorators.
  • Debugging: Can be more difficult to debug than simpler patterns.

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.