Design Pattern : Decorator Pattern

Design Pattern : Decorator Pattern

This is a structural pattern that attaches additional responsibilities to an object dynamically by placing them in wrappers that contain those behaviours. This provides an alternative to subclassing. In SOLID design principles: classes should be open for extension and closed modification.

Use case

Kwame is designing a pizza ordering software to be used by customers to order pizza from any part of the city. Customers could select a pizza type and add toppings as much as the want. Kwame starts with the design.

 Pizza {
  description;
  cheese;
  beef;

  getDescription();
  getCheese();
  setCheese();
  getBeef();
  setBeef();

  //other methods
}

VegetarianPizzaNoToppings extends Pizza {
  cost()
}

MeatPizzaBeefToppings extends Pizza {
  cost()
}

MeatPizzaBeefAndCheeseToppings extends Pizza {
  cost()
}

MeatPizzaCheeseToppings extends Pizza {
  cost()
}

This approach extends the Pizza class and add different types of pizza. The design here is not maintainable:

  1. New Pizza types will force us to add methods to the Pizza class and alter the cost();

  2. MeatPizzaCheeseToppings does not need getBeef() but unfortunately its been inherited from the super class.

  3. Price changes will force us to alter existing code.

Imagine the number of classes you could think of writing for this program .

Solution

We are going to use the decorator pattern to solve our use case. Firstly we are going to create a Pizza interface where our pizza types can implement. We will then create a decorator which toppings can extend. Lets begin.

Step 1.

Create a base interface.

public interface Pizza {
    public String getDescription();
    public double cost();
}

Step 2

Implement the base interface

public class VegetarianPizza implements Pizza {
    @Override
    public String getDescription() {
        return "Vegetarian pizza";
    }
    @Override
    public double cost() {
        return 4.2;
    }
}


public class MeatPizza implements Pizza {
    @Override
    public String getDescription() {
        return "Meat Pizza";
    }
    @Override
    public double cost() {
        return 5.2;
    }
}

Step 3

Create an abstract decorator class which also implements the base interface

public abstract class PizzaDecorator implements Pizza {
    protected Pizza pizza;
    public PizzaDecorator(Pizza pizza) {
        this.pizza = pizza;
    }
}

Step 4

Create concrete classes which extends the decorator class. This where we can create multiple Topping class as much as we want.

public class Beef extends PizzaDecorator {
    public Beef(Pizza pizza) {
        super(pizza);
    }

    @Override
    public String getDescription() {
        return pizza.getDescription() + ", beef";
    }

    @Override
    public double cost() {
        return 0.3 + pizza.cost();
    }

}

public class Pork extends PizzaDecorator {

    public Pork(Pizza pizza) {
        super(pizza);
    }

    @Override
    public String getDescription() {
        return pizza.getDescription() + ", pork";
    }

    @Override
    public double cost() {
        return 0.25 + pizza.cost();
    }

}

public class Cheese extends PizzaDecorator {

    public Cheese(Pizza pizza) {
        super(pizza);
    }

    @Override
    public String getDescription() {
        return pizza.getDescription() + ", cheese";
    }

    @Override
    public double cost() {
        return 0.17 + pizza.cost();
    }

}

Step 5

Decorate our pizzas with toppings.

public class Main {

    public static void main(String[] args) {
        Pizza meatPizza = new MeatPizza();
        meatPizza = new Beef(meatPizza);
        meatPizza = new Pork(meatPizza);

        Pizza greenPizza = new VegetarianPizza();
        greenPizza = new Cheese(greenPizza);
        greenPizza = new Cheese(greenPizza); //we are going for double cheese 

        System.out.println("Pizza One order : " + meatPizza.getDescription());
        System.out.println("Pizza One price: " + meatPizza.cost());

        System.out.println("Pizza Two order : " + greenPizza.getDescription());
        System.out.println("Pizza Two price: " + greenPizza.cost());
    }

}

Step 6

Output

Pizza One order : Meat Pizza, beef, pork
Pizza One price: 5.75
Pizza Two order : Vegetarian pizza, cheese, cheese
Pizza Two price: 4.54

Conclusion

This blog focuses on the decorator design pattern where responsibilities are added to classes dynamically at runtime. This functionality enables us to obey the Open-Close principle where our classes are open for extension but closed for modification.