What is Decorator Pattern?

The Decorator pattern in Java is defined as it attaches additional responsibilities to an object dynamically. Decorator pattern provides a flexible alternative to subclassing for extending functionality.

A problem to understand the need to implement Decorator Pattern in Java

A Dominico’s pizza company has started to build an ordering system. Initially they started the ordering system with some 5 pizza variety, later they were expanding their pizza varieties, topping’s varieties, cheese levels to their ordering system. Hence their ordering system should behave unique to every customer’s choice. Every customer decorates their pizza with different toppings and cheese levels, so the cost varies for every toppings and cheese levels.

Challenges in Dominico’s Pizza ordering system

  1. Cost changes for every toppings, every type of crust which will force us to alter existing code
  2. We may have new pizza type to add, toppings and crust needs to be added to it. It shouldn’t make us to alter the existing code of other pizza’s, toppings and crust
  3. What if customer wants 2 double cheese to be added

Our GOAL in decorator pattern

Allow classes to be easily extended to incorporate new behavior without modifying existing code.

Classes should be open for extension, but closed for modification which we call as OPEN-CLOSED Principle

Note: Be careful when choosing the area of code that need to be extended; applying the open-closed principle EVERYWHERE, is wasteful and unnecessary, and can lead to complex, hard-to-understand code.

Overview of Steps for Implementing Decorator Pattern in Java for Dominico’s Pizza Shop

Create a super-type Pizza

Create decorators(toppings, crust, cheese) as type of Pizza(super-type)

Decorators have the same super-types as the object they decorate

You can use one or more decorators to wrap an object

Given the decorator has the same supertype as the object it decorates, we can pass around a decorated object in place of the original (wrapped) object

The decorator adds its own behavior either before and/or after delegating to the object it decorates to do the rest of the job

Objects can be decorated at any time, so we can decorate objects dynamically at run time with as many decorators we like

Let’s create a super-type Pizza which doesn’t need to change from Dominico’s Original design.

public abstract class Pizza {
  
  String description = "Pizza not found";
  
  public String getDescription() {
    return description;
  }
  
  public abstract double cost();

}

Pizza is simple enough. Let’s implement the abstract class for the Toppings, Crust, Cheese (Decorator) as well:

public abstract class PizzaDecorator extends Pizza{

  public abstract String getDescription();
  
}

Let’s implement some Pizza’s

public class VegPizza extends Pizza{
  
  public VegPizza() {
    description = "Veg Pizza";
  }

  @Override
  public double cost() {
    return 2.00;
  }

}
public class NonVegPizza extends Pizza{

  public NonVegPizza() {
    description = "Non Veg Pizza";
  }
  
  @Override
  public double cost() {
    return 3.00;
  }

}

Coding the decorators

public class VegToppings extends PizzaDecorator{

  Pizza pizza;
  
  public VegToppings(Pizza pizza) {
    this.pizza = pizza;
  }
  
  @Override
  public String getDescription() {
    return pizza.getDescription()+" + Veg Toppings";
  }
  
    public double cost() {
      return pizza.cost() + .30;
    }
}
public class Cheese extends PizzaDecorator{

  Pizza pizza;
  
  public Cheese(Pizza pizza) {
    this.pizza = pizza;
  }
  
  @Override
  public String getDescription() {
    return pizza.getDescription()+" + extra Cheese";
  }
  
    public double cost() {
      return pizza.cost() + .20;
    }
}

Serving the Pizza’s

public class DominicoPizzaShop {

  public static void main(String[] args) {
    
    Pizza pizza1 = new VegPizza();
    System.out.println(pizza1.getDescription()+" - Cost $"+pizza1.cost());
    
    Pizza pizza2 = new NonVegPizza();
    pizza2 = new VegToppings(pizza2);
    pizza2 = new Cheese(pizza2);
    System.out.println(pizza2.getDescription()+" - Cost $"+pizza2.cost());
    
  }
}

Output

Veg Pizza - Cost $2.0
Non Veg Pizza + Veg Toppings + extra Cheese - Cost $3.5

Note: Better way of creating decorated objects can be made with Factory and Builder design pattern.

Real World Decorators: Java I/O

The large numbers of classes in the java.io package is made of decorator objects.

InputStream and OutputStream acts as the abstract components that we will wrap with decorators.

Downside of Decorator Pattern in Java

Java I/O points out one of the downsides of the decorator pattern: designs using this pattern often results in a large number of small classes that can be difficult to developers trying to use the Decorator-based API.

Conclusion

The decorator pattern involves a set of decorator classes that are used to wrap concrete components. Decorator classes mirror the type of the components they decorate.

 

Please follow and like us:
error