The Decorator Design Pattern adds additional functionalities dynamically to an object. A decorator extends the responsibilities of a class by using composition not inheritance.
A decorator object contains an original object instance as member variable and it adds new functionalities either before or/and after delegating it to the original object. It comes under structural design pattern as it provides one of the best ways to extend the responsibilities of a class.
Advantages of Decorator Pattern
- It allows classes to extend easily to support new requirements without modifying existing code.
- A decorator implements the same interface as the object it decorates. We can use the decorator object(typecasted to supertype) where original object was expected.
When we should use Decorator Pattern
- When we want to dynamically add or remove functionalities to a class without affecting the behaviour of other objects from the same class.
- When we want to decouple Concrete implementations from responsibilities and behaviours.
- We want to add additional functionalities to a class without affecting any of the clients.
- When extending the functionalities of a class using inheritance will end up in creating lots of sub-classes.
Implementation of Decorator Design Pattern
In this example, we already have a PlainPizza class that implements Pizza interface. Now we want to add an extra functionality of adding extra cheese to pizza by keeping the interface same as Pizza.
We will first create an Pizza interface and it’s concrete implementation PlainPizza.
Pizza.java public interface Pizza { public float getPrice(); public void preparePizza(); public void packPizza(); } PlainPizza.java public class PlainPizza implements Pizza { @Override public float getPrice(){ return 10; } @Override public void preparePizza(){ System.out.println("Prepairing Pizza"); } @Override public void packPizza(){ System.out.println("Packing Pizza"); } }
Create abstract PizzaDecorator class implementing the Pizza interface and concrete decorator class extending PizzaDecorator class. This will ensure that all decorator classes will have common Pizza interface.
PizzaDecorator.java public abstract class PizzaDecorator implements Pizza { protected Pizza pizza; public PizzaDecorator(Pizza pizza) { this.pizza = pizza; } public float getPrice(){ return pizza.getPrice(); } public void preparePizza(){ pizza.preparePizza(); } public void packPizza(){ pizza.packPizza(); } }
CheeseBurstPizza.java class overrides the Pizza interface methods to provide additional functionalities or adding more cheese toppings.
CheeseBurstPizza.java public class CheeseBurstPizza extends PizzaDecorator { public CheeseBurstPizza(Pizza pizza){ super(pizza); } @Override public float getPrice(){ // Extra money for Cheese Burst return pizza.getPrice() + 5.0f; } @Override public void preparePizza(){ System.out.println("Adding Extra Cheese.."); pizza.preparePizza(); } @Override public void packPizza(){ pizza.packPizza(); } }
DecoratorPatternExample.java class creates a plane pizza and Cheese Burst Pizza using Pizza interface.
DecoratorPatternExample.java public class DecoratorPatternExample { public static void main(String[] args) { // Prepairing a Plain pizza Pizza plainPizza = new PlainPizza(); plainPizza.preparePizza(); plainPizza.packPizza(); System.out.println(plainPizza.getPrice()); System.out.println("---------------------------"); // Prepairing a Cheese Burst Pizza using Pizza interface only Pizza cheeseBurstPizza = new CheeseBurstPizza(new PlainPizza()); cheeseBurstPizza.preparePizza(); cheeseBurstPizza.packPizza(); System.out.println(cheeseBurstPizza.getPrice()); } }
Output
Prepairing Pizza Packing Pizza 10.0 --------------------------- Adding Extra Cheese.. Prepairing Pizza Packing Pizza 15.0