Home  Java   Decorator p ...

decorator pattern in Java

The Decorator pattern is a structural pattern that allows you to add new functionalities or responsibilities to an object dynamically without altering its core structure. It achieves this by wrapping the original object within a special decorator object.

It's an alternative to inheritance for extending functionality.


Real-Life Analogy: Ordering Coffee ☕

Imagine ordering a basic coffee. You can add extra features (decorations) like milk, whipped cream, or flavor shots to the basic coffee without changing the coffee itself. Each addition wraps the previous one.

The Structure

ComponentRole in Coffee Analogy
CoffeeComponent Interface: Defines the core product and methods (cost(), getDescription()).
SimpleCoffeeConcrete Component: The basic, undecorated object (the plain black coffee).
CoffeeDecoratorDecorator Abstract Class: Holds a reference to the Coffee object it's wrapping and implements the Coffee interface.
Milk / WhipConcrete Decorators: Add specific behavior/cost to the wrapped coffee.

Java Code Example: Coffee Shop

1. The Component Interface

This defines the contract for all coffee items, decorated or not.

// 1. Component Interface
interface Coffee {
    double cost();
    String getDescription();
}

2. The Concrete Component

The basic, core object.

// 2. Concrete Component
class SimpleCoffee implements Coffee {
    @Override
    public double cost() {
        return 5.00; // Base cost
    }

    @Override
    public String getDescription() {
        return "Simple Coffee";
    }
}

3. The Abstract Decorator

This is the key piece: it implements the Coffee interface and wraps another Coffee object.

// 3. Decorator Abstract Class
abstract class CoffeeDecorator implements Coffee {
    protected final Coffee decoratedCoffee;

    public CoffeeDecorator(Coffee coffee) {
        this.decoratedCoffee = coffee;
    }
    
    // Delegates to the wrapped object by default
    @Override
    public double cost() {
        return decoratedCoffee.cost();
    }
    
    @Override
    public String getDescription() {
        return decoratedCoffee.getDescription();
    }
}

4. Concrete Decorators

These add the specific new behavior and cost.

// 4. Concrete Decorator 1: Adds Milk
class Milk extends CoffeeDecorator {
    public Milk(Coffee coffee) {
        super(coffee);
    }

    @Override
    public double cost() {
        // Adds $1.00 to the wrapped coffee's cost
        return super.cost() + 1.00;
    }

    @Override
    public String getDescription() {
        // Adds "with Milk" to the wrapped coffee's description
        return super.getDescription() + ", with Milk";
    }
}

// 4. Concrete Decorator 2: Adds Whipped Cream
class Whip extends CoffeeDecorator {
    public Whip(Coffee coffee) {
        super(coffee);
    }

    @Override
    public double cost() {
        // Adds $1.50 to the wrapped coffee's cost
        return super.cost() + 1.50;
    }

    @Override
    public String getDescription() {
        // Adds "with Whip" to the wrapped coffee's description
        return super.getDescription() + ", with Whip";
    }
}

5. Client Usage

The client can stack decorators to create complex objects.

public class DecoratorDemo {
    public static void main(String[] args) {
        // Start with a basic coffee
        Coffee myCoffee = new SimpleCoffee();

        System.out.println("Order 1: " + myCoffee.getDescription() + " - $" + myCoffee.cost()); 
        // Output: Order 1: Simple Coffee - $5.0

        // Wrap the coffee with Milk
        myCoffee = new Milk(myCoffee);
        System.out.println("Order 2: " + myCoffee.getDescription() + " - $" + myCoffee.cost()); 
        // Output: Order 2: Simple Coffee, with Milk - $6.0

        // Now, wrap the Milk-coffee with Whip (stacking)
        myCoffee = new Whip(myCoffee);
        System.out.println("Order 3: " + myCoffee.getDescription() + " - $" + myCoffee.cost()); 
        // Output: Order 3: Simple Coffee, with Milk, with Whip - $7.5
    }
}

This stacking is why the Decorator pattern is used extensively in Java I/O streams (e.g., wrapping a FileInputStream in a BufferedInputStream to add buffering functionality).

Published on: Oct 05, 2025, 11:01 AM  
 

Comments

Add your comment