What is Decorator pattern?

The Decorator pattern is a structural design pattern that allows behavior to be added to an object dynamically at runtime. It provides an alternative to subclassing for extending the functionality of an object.

The Decorator pattern involves creating a decorator class that wraps the original object and provides additional or modified behavior while maintaining the interface of the original object. This allows you to add or alter functionality by composing objects rather than relying on inheritance.

Key components of the Decorator pattern:

  1. Component: The component is the interface or abstract class that defines the interface for objects that can have additional responsibilities attached to them.
  2. Concrete Component: The concrete component is the original object to which additional behavior will be added. It implements the component interface.
  3. Decorator: The decorator is an abstract class or interface that wraps the concrete component and provides additional behavior. It has a reference to the component and implements the same interface. The decorator may also provide a default implementation for the interface methods and delegate calls to the component.
  4. Concrete Decorators: Concrete decorators are subclasses of the decorator that add specific behavior to the component. They extend the decorator class and override or add functionality to the component’s methods. Multiple concrete decorators can be stacked on top of each other to add multiple layers of behavior.

Benefits and use cases of the Decorator pattern:

  1. Flexible extension of functionality: The Decorator pattern allows you to add or modify behavior of an object at runtime without changing its interface or existing code. You can mix and match decorators to add specific features or combinations of features to an object, providing flexibility in extending functionality.
  2. Single Responsibility Principle (SRP): The Decorator pattern follows the SRP by promoting the separation of concerns. Each decorator class is responsible for a specific aspect of behavior, allowing you to isolate and manage different responsibilities independently.
  3. Open/Closed Principle (OCP): The Decorator pattern supports the OCP by allowing behavior to be added to an object without modifying its source code. You can introduce new decorators to add new features or modify existing decorators without changing the original component.
  4. Code reuse and composition: Decorators can be composed and stacked on top of each other to create complex combinations of behavior. This promotes code reuse as decorators can be used with different components and in different configurations to achieve desired functionality.
  5. Dynamic behavior modification: Decorators can be added or removed at runtime, allowing for dynamic modification of an object’s behavior. This provides flexibility to adapt the behavior based on specific conditions or requirements.

Example of the Decorator pattern in Java:

// Component interface
public interface Pizza {
    String getDescription();
    double getCost();
}

// Concrete component
public class MargheritaPizza implements Pizza {
    public String getDescription() {
        return "Margherita Pizza";
    }

    public double getCost() {
        return 8.99;
    }
}

// Decorator
public abstract class PizzaDecorator implements Pizza {
    protected Pizza pizza;

    public PizzaDecorator(Pizza pizza) {
        this.pizza = pizza;
    }

    public String getDescription() {
        return pizza.getDescription();
    }

    public double getCost() {
        return pizza.getCost();
    }
}

// Concrete decorator
public class CheeseDecorator extends PizzaDecorator {
    public CheeseDecorator(Pizza pizza) {
        super(pizza);
    }

    public String getDescription() {
        return pizza.getDescription() + ", Cheese";
    }

    public double getCost() {
        return pizza.getCost() + 1.5;
    }
}

// Concrete decorator
public class TomatoDecorator extends PizzaDecorator {
    public TomatoDecorator(Pizza pizza) {
        super(pizza);
    }

    public String getDescription() {
        return pizza.getDescription() + ", Tomato";
    }

    public double getCost() {
        return pizza.getCost() + 0.5;
    }
}

// Usage:
Pizza margheritaPizza = new MargheritaPizza();
Pizza cheesePizza = new CheeseDecorator(margheritaPizza);
Pizza cheeseAndTomatoPizza = new TomatoDecorator(cheesePizza);

System.out.println(cheeseAndTomatoPizza.getDescription()); // Output: Margherita Pizza, Cheese, Tomato
System.out.println(cheeseAndTomatoPizza.getCost()); // Output: 11.99

In the above example, the Decorator pattern is used to add toppings to a pizza. The Pizza interface defines the methods getDescription() and getCost() for pizzas. The MargheritaPizza class is the concrete component that implements the Pizza interface.

The PizzaDecorator class is the abstract decorator that extends the Pizza interface and maintains a reference to a Pizza object. It provides default implementations for the interface methods and delegates the calls to the decorated pizza object.

The CheeseDecorator and TomatoDecorator classes are concrete decorators that extend the PizzaDecorator. They add specific behavior (cheese and tomato toppings) to the decorated pizza. They override the methods of the PizzaDecorator to modify the behavior and add their own functionality.

In the usage example, a MargheritaPizza object is created and then wrapped with a CheeseDecorator and a TomatoDecorator to add cheese and tomato toppings. The decorators dynamically modify the behavior and provide the combined description and cost of the decorated pizza.

By using the Decorator pattern, we can add new toppings or combinations of toppings to pizzas without modifying their source code. The behavior of the pizzas can be extended dynamically at runtime, allowing for flexible and reusable customization.

error: Content is protected !!