Decorator Pattern

The Decorator design pattern is a structural design pattern that allows you to add behavior or responsibilities to objects dynamically, without altering their code. It is one of the Gang of Four design patterns and is used to achieve flexibility in object composition by allowing behavior to be added or removed from individual objects, rather than modifying the entire class hierarchy.

This pattern is particularly useful when you have a set of objects with similar functionalities, but you want to provide a way to extend or modify their behavior in a flexible manner.

It provides an alternative to subclassing for extending functionality, allowing behavior to be added or overridden at runtime.

Structure

The key components of the Decorator pattern are:

  1. Component: This is the base interface or abstract class that defines the common interface for all concrete components and decorators.

  2. Concrete Component: This is the basic implementation of the Component interface. It defines the core behavior that decorators can augment.

  3. Decorator: This is an abstract class or interface that extends the Component interface and holds a reference to a Component object. It has the same interface as the Component, allowing it to wrap around concrete components and add additional behavior.

  4. Concrete Decorator: These are classes that extend the Decorator class and provide concrete implementations of additional behaviors. They can add new methods, fields, or modify the behavior of the wrapped Component.

Examples

Breverage Ordering System

Let’s take the example of a beverage ordering system. We have a base component Beverage with two concrete components: Coffee and Tea.

classDiagram
  class Beverage {
    <> Beverage
    +getDescription() : String
    +cost() : double
  }

  class Coffee {
    +getDescription() : String
    +cost() : double
  }

  class Tea {
    +getDescription() : String
    +cost() : double
  }

  Beverage <|.. Coffee
  Beverage <|.. Tea

We want to add decorators for adding additional options like milk, sugar, and whipped cream.

classDiagram
  class BeverageDecorator {
    -beverage : Beverage
    +BeverageDecorator(beverage: Beverage)
    +getDescription() : String
  }

  class MilkDecorator {
    +MilkDecorator(beverage: Beverage)
    +getDescription() : String
    +cost() : double
  }

  class SugarDecorator {
    +SugarDecorator(beverage: Beverage)
    +getDescription() : String
    +cost() : double
  }

  class Beverage {
    <> Beverage
    +getDescription() : String
    +cost() : double
  }
  
  BeverageDecorator <|-- MilkDecorator
  BeverageDecorator <|-- SugarDecorator
  BeverageDecorator o-- Beverage
  1. Component (Beverage):
  1. Concrete Components (Coffee and Tea):
  1. Decorator (BeverageDecorator):
  1. Concrete Decorators (MilkDecorator, SugarDecorator):

Usage:

In this example, the Decorator pattern allowed us to dynamically add milk and sugar as decorators to the coffee object, altering its description and cost without changing the original Coffee class.

Pizzas and Toppings

classDiagram
    class Pizza {
        <>
        +getDescription() : String
        +cost() : double
    }

    class MargheritaPizza {
        +getDescription() : String
        +cost() : double
    }

    class PepperoniPizza {
        +getDescription() : String
        +cost() : double
    }
  
    class PizzaDecorator {
        -pizza : Pizza
        +PizzaDecorator(pizza: Pizza)
        +getDescription() : String
    }

    class CheeseTopping {
        +CheeseTopping(pizza: Pizza)
        +getDescription() : String
        +cost() : double
    }

    class MushroomTopping {
        +MushroomTopping(pizza: Pizza)
        +getDescription() : String
        +cost() : double
    }

    class JalapenoTopping {
        +JalapenoTopping(pizza: Pizza)
        +getDescription() : String
        +cost() : double
    }

    Pizza <|.. MargheritaPizza
    Pizza <|.. PepperoniPizza

    PizzaDecorator <|-- CheeseTopping
    PizzaDecorator <|-- MushroomTopping
    PizzaDecorator <|-- JalapenoTopping
    PizzaDecorator o-- Pizza

Implementation

1. Base Pizza Component:

This represents the basic pizza. It will be our base component, and we’ll add toppings to it using decorators.

2. Concrete Pizza Types:

We’ll have two types of pizzas: MargheritaPizza and PepperoniPizza.

3. Toppings (Decorators):

Now, let’s define some toppings that can be added to the pizzas.

Usage:

Now let’s create some pizzas and add various toppings to them.

Benefits of the Decorator Pattern

  1. Flexible extension: You can add or remove responsibilities from objects at runtime, making it easy to create different combinations of behaviors.

  2. Open-closed principle: You can extend the behavior of objects without modifying their source code, promoting the open-closed principle of software design.

  3. Single Responsibility Principle: The pattern allows for separation of concerns by breaking down functionalities into small, focused classes.

Drawbacks of the Decorator Pattern

  1. Complexity: The pattern can lead to a large number of small classes if not carefully managed, which might be difficult to understand and maintain.

  2. Order of wrapping: The order in which decorators are added can impact the final behavior, and this might be confusing in some scenarios.

In conclusion, the Decorator design pattern is a powerful tool for enhancing the functionality of individual objects while maintaining a clear separation of concerns and adhering to the principles of object-oriented design.