# DecoratorA intermediate-level guide to Decorator: before-and-after java code and diagrams for a CS student.

## The "Class Explosion" Nightmare

Imagine you are building a coffee shop application. You start with a simple `Coffee` class that handles the base cost and description. Then, customers want add-ons: milk, sugar, caramel, or whipped cream. 

If you use standard inheritance to handle every possible combination, your codebase will quickly spiral out of control. For every new ingredient, you have to create a new subclass. If you have ten ingredients, the number of subclasses required to cover every mathematical combination becomes astronomical. This is known as "class explosion."

```java
class Coffee {
    public String getDescription() { return "Plain Coffee"; }
    public double getCost() { return 1.0; }
}

// Class explosion: one subclass for every possible combination of toppings
class CoffeeWithMilk extends Coffee {
    public String getDescription() { return getDescription() + ", Milk"; }
    public double getCost() { return getCost() + 0.5; }
}

class CoffeeWithSugar extends Coffee {
    public String getDescription() { return getDescription() + ", Sugar"; }
    public double getCost() { return getCost() + 0.2; }
}

class CoffeeWithMilkAndSugar extends Coffee {
    public String getDescription() { return getDescription() + ", Milk and Sugar"; }
    public double getCost() { return getCost() + 0.5 + 0.2; }
}
// Imagine needing: WithCaramel, WithWhip, WithSoy, etc. The hierarchy becomes unmanageable.
```

As shown in the diagram below, the hierarchy grows horizontally and vertically for every minor variation, making the system rigid and nearly impossible to maintain:

```mermaid
classDiagram
    class Coffee {
        +getDescription() String
        +getCost() double
    }
    class CoffeeWithMilk extends Coffee
    class CoffeeWithSugar extends Coffee
    class CoffeeWithMilkAndSugar extends Coffee
```

## Wrapping for Power

Instead of trying to predict every possible combination using inheritance, we can use **Composition**. Composition is the practice of holding an object as a field within another class. 

Think of it like Russian Nesting Dolls (Matryoshka dolls). You don't create a "Milk-and-Sugar-Coffee" doll; instead, you take a "Plain Coffee" doll and wrap it in a "Milk" layer, then wrap that entire set in a "Sugar" layer. Each layer adds something new while keeping the inner doll intact.

In software terms, this is the Decorator pattern. To make this work, we need an **Interface**. An interface is a contract that defines what methods a class must have without specifying how they work. By ensuring both our base object and our "wrappers" implement the same interface, they become interchangeable.

## How it Works: The Interface Trick

The Decorator pattern uses a clever structural trick: the decorator implements the interface AND contains an instance of that same interface inside itself. This allows you to stack them indefinitely. Because the decorator *is-a* type of the interface (via implementation) and *has-a* type of the interface (via composition), it can delegate work to the object it is wrapping.

```java
interface Coffee {
    String getDescription();
    double getCost();
}

class PlainCoffee implements Coffee {
    public String getDescription() { return "Plain Coffee"; }
    public double getCost() { return 1.0; }
}

abstract class CoffeeDecorator implements Coffee {
    protected final Coffee decoratedCoffee;

    public CoffeeDecorator(Coffee coffee) {
        this.decoratedCoffee = coffee;
    }

    public String getDescription() { return decoratedCoffee.getDescription(); }
    public double getCost() { return decoratedCoffee.getCost(); }
}

class MilkDecorator extends CoffeeDecorator {
    public MilkDecorator(Coffee coffee) { super(coffee); }
    @Override
    public String getDescription() { return decoratedCoffee.getDescription() + ", Milk"; }
    @Override
    public double getCost() { return decoratedCoffee.getCost() + 0.5; }
}

class SugarDecorator extends CoffeeDecorator {
    public SugarDecorator(Coffee coffee) { super(coffee); }
    @Override
    public String getDescription() { return decoratedCoffee.getDescription() + ", Sugar"; }
    @Override
    public double getCost() { return decoratedCoffee.getCost() + 0.2; }
}
```

When a method is called on a decorated object, the call travels through the layers like a relay race. The decorator performs its specific logic (like adding $0.5 to the cost) and then calls the same method on the wrapped object to get the rest of the data.

```mermaid
sequenceDiagram
    participant C as SugarDecorator
    participant M as MilkDecorator
    participant P as PlainCoffee

    C ->> M: getCost()
    M ->> P: getCost()
    P -->> M: 1.0
    M ->> M: + 0.5
    M -->> C: 1.5
    C ->> C: + 0.2
    C -->> C: 1.7
```

The class diagram below illustrates how the decorators maintain a relationship with the `Coffee` interface, allowing them to wrap any object that adheres to that contract:

```mermaid
classDiagram
    class Coffee {
        <<interface>>
        +getDescription() String
        +getCost() double
    }
    class PlainCoffee implements Coffee
    class CoffeeDecorator implements Coffee {
        #decoratedCoffee : Coffee
    }
    class MilkDecorator extends CoffeeDecorator
    class SugarDecorator extends CoffeeDecorator

    CoffeeDecorator --> Coffee : decoratedCoffee
```

## When to Use It, and When Not To

The Decorator pattern is a powerful tool for following the Open/Closed Principle (the idea that classes should be open for extension but closed for modification). You should reach for it when you need to add responsibilities to objects dynamically at runtime.

However, decorators come with costs:
* **Complexity:** You endจัย up with many small, specialized objects. While this is better than a class explosion, it can make debugging slightly harder as the "real" object might be buried under five layers of wrappers.
* **The God Wrapper Risk:** Avoid creating a decorator that tries to do too much. Each decorator should have one single responsibility (e.g., just milk, or just sugar). If a decorator starts handling multiple unrelated behaviors, you are recreating the very complexity you sought to avoid.

## Takeaways

- Use Decorators to add behavior at runtime through composition rather than static inheritance.
- To implement this pattern, both the base object and the decorator must implement the same interface.
- This approach prevents "class explosion" by allowing you to mix and match features without creating a subclass for every combination.

## Usage

**Stacking decorators at runtime to create custom combinations**

```java
Coffee myOrder = new SugarDecorator(new MilkDecorator(new PlainCoffee()));
System.out.println(myOrder.getDescription()); // Plain Coffee, Milk, Sugar
System.out.println(myOrder.getCost());        // 1.7
```
