Decorator Design Pattern in Java

Decorator Design Pattern in Java | Decorator Design Pattern is a structural pattern that allows us to add new functionalities or responsibilities to existing objects without affecting the behavior of other existing objects. It means we can add dynamic functionalities or responsibilities to the existing object as the wrapper around the existing object, so it is called decorator or wrapper design pattern. It is one of the popularly used structural patterns. 

Need of Decorator Design Pattern

Problem:- Always adding new functionalities/responsibilities on the existing object through inheritance is bad practice and we will end up getting the huge number of classes.

Let us understand this problem through an example. Assume we have a normal IceCream interface. To understand this problem we can also take the example of Pizza.

// IceCream.java
public interface IceCream {
   public void prepare();
}

Some of the top flavors which can be added to the normal Ice Cream are:- Vanilla, Chocolate, Strawberry, Mint Chocolate Chip, Moose tracks, Neapolitan, Chocolate Chip Cookie Dough, Buttered Pecan, Cookies & Cream, and e.t.c.

These functionalities can be added to the normal Ice Cream through inheritance as given below,

//  VanillaIceCream.java
public class VanillaIceCream implements IceCream {
   @Override
   public void prepare() {
      System.out.println("Preparing Vanilla IceCream");
   }
}

Similarly, the other flavors can be added to the normal Ice Cream. Through inheritance, it can be written as,

  • public class ChocolateIceCream implements IceCream { }
  • public class StrawberryIceCream implements IceCream { }
  • public class MintChocolateChipIceCream implements IceCream { }
  • public class CookiesCreamIceCream implements IceCream { }
  • and so on.

On one flavor we can add one or multiple toppings. Some of the top toppings used on IceCream are:- Oreo, Cookie dough, Chocolate Syrup, Dry Fruit, Jellies, Hot fudge, Sprinkles, Caramel, Peanut butter cups, Whipped cream, Hard chocolate coating, Melted marshmallows, Cereal, Nuts, and e.t.c. More than 100 toppings are available.

On VanillaIceCream these toppings can be added through inheritance as,

//  OreoVanillaIceCream.java
public class OreoVanillaIceCream extends VanillaIceCream {
   @Override
   public void prepare() {
      super.prepare();
      addOreo();
   }
   public void addOreo() {
      System.out.println("Adding Oreo");
   }
}

Similarly, the other toppings can be added through inheritance as follows,

  • public class CookieDoughVanillaIceCream extends VanillaIceCream { }
  • public class DryFruitVanillaIceCream extends VanillaIceCream { }
  • public class JelliesVanillaIceCream extends VanillaIceCream { }
  • and so on.

Some people like more than one topping on the IceCream,

// VanillaIceCream + Oreo + Dry Fruit
public class DryFruitOreoVanillaIceCream 
             extends OreoVanillaIceCream { }
// VanillaIceCream + Oreo + Dry Fruit + Chocolate Syrup
public class ChocolateSyrupDryFruitOreoVanillaIceCream 
             extends DryFruitOreoVanillaIceCream { }

With these many permutations and combinations, more than 1000 classes are possible. Therefore inheritance is not always the best solution to add new functionalities or responsibilities to the existing object. To overcome the problem of developing the huge number of classes we should go for Decorator or Wrapper design pattern which builds the solutions using composition (HAS-A) and a bit of inheritance (IS-A).

class A {
   // ...
}

// Example of Inheritance (IS-A) relation
class B extends A {
   // ...
}

// Example of Composition (HAS-A) relation
class B {
   A a = new A(); // HAS-A
   // ...
}

Components of Decorator or Wrapper Design Pattern

Various components used in Decorator or Wrapper Design Pattern are:-

  1. Component:- It is the basic interface that defines object type (like IceCream(I)).
  2. Concrete Component:- It implements and defines the basic component flavor/root objects. In our example different flavor ice creams are Concrete Component. Example:- VanillaIceCream, ChocolateIceCream, StrawberryIceCream, MintChocolateChipIceCream, CookiesCreamIceCream, and e.t.c.
  3. Abstract Decorator:- It is abstract class implementing component(I) and also containing component(I) type property. It is basic class for all decorator/wrapper classes that are there to add extra functionalities or responsibilities to the object. It is like IceCreamDecorator (AC). It is not there in above problem example, but in solution part it will be there.
  4. Concrete Decorator:- It extends from Abstract Decorator defining additional functionalities or responsibilites on the top of ConcreteComponent. In our example these are like OreoVanillaIceCream, CookieDoughVanillaIceCream, DryFruitOreoVanillaIceCream, ChocolateSyrupDryFruitOreoVanillaIceCream, and e.t.c.

Example of Wrapper or Decorator Design Pattern in Java

Let us solve the problem we faced in the previous IceCream example. The structure of the application is,

DecoratorDP
|=> src
   |=> com.kp.comps
       => IceCream.java(I)
       => VanillaIceCream.java
       => StrawberryIceCream.java
       => ChocolateIceCream.java
   |=> com.kp.decorator
       => IceCreamDecorator.java(AC)
       => OreoDecorator.java
       => DryFruitDecorator.java
       => CookieDoughDecorator.java
   |=> com.kp.test
       => Shop.java

Here,

  • Component:- IceCream.java (Interface) 
  • Concrete Component:- VanillaIceCream.java, StrawberryIceCream.java, ChocolateIceCream.java (Concrete classes)
  • Abstract Decorator:- IceCreamDecorator.java (Abstract Decorator class)
  • Concrete Decorator:- OreoDecorator.java, DryFruitDecorator.java,  CookieDoughDecorator.java (Concrete Decorator classes)

By just taking only these classes multiple combinations are possible which was not available only through inheritance. You can observe it while testing (in Shop.java). Let us develop the components,

// IceCream.java (Main Component interface)
package com.kp.comps;
public interface IceCream {
   public void prepare();
}
// VanillaIceCream.java (Concrete Component)
package com.kp.comps;
public class VanillaIceCream implements IceCream {
   @Override
   public void prepare() {
      System.out.println("Preparing Vanilla Ice Cream");
   }
}

Based on VanillaIceCream.java, develop StrawberryIceCream.java, ChocolateIceCream.java.

Now, the most important part of our example is to develop IceCreamDecorator.java. In the com.kp.decorator package,

// IceCreamDecorator.java (Abstract Decorator)
package com.kp.decorator;
import com.kp.comps.IceCream;
public abstract class IceCreamDecorator implements IceCream {
   // property which will be used to 
   // link one decorator with another decorator
   private IceCream icecream; // composition (HAS-A)

   // constructor
   public IceCreamDecorator(IceCream icecream) {
      this.icecream = icecream;
   }

   @Override
   public void prepare() {
      icecream.prepare();
   }
}

Develop OreoDecorator.java, DryFruitDecorator.java, and CookieDoughDecorator.java extending from IceCreamDecorator.java. The code for OreoDecorator.java is given below, for other classes logic is similar.

The OreoDecorator.java will be used to add Oreo topping on any type of IceCream, similarly,  DryFruitDecorator.java will be used to add dry fruit on any type of IceCream, and so on.

// OreoDecorator.java (Concrete Decorator)
package com.kp.decorator;
import com.kp.comps.IceCream;
// To add Oreo topping on any type of IceCream
public class OreoDecorator extends IceCreamDecorator {
   public OreoDecorator(IceCream icecream) {
      super(icecream);
   }

   @Override
   public void prepare() {
      super.prepare();
      addOreo();
   }

   public void addOreo() {
      System.out.println("Adding Oreo");
   }
}

Now, let us develop the client/user class (Shop.java). In the com.kp.test package,

//  Shop.java
package com.kp.test;

import com.kp.comps.ChocolateIceCream;
import com.kp.comps.IceCream;
import com.kp.comps.VanillaIceCream;
import com.kp.decorator.CookieDoughDecorator;
import com.kp.decorator.DryFruitDecorator;
import com.kp.decorator.OreoDecorator;

public class Shop {
   public static void main(String[] args) {
      // Customer-1 wants VanillaIceCream without topping
      System.out.println("Customer-1");
      IceCream iceCream1 = new VanillaIceCream();
      iceCream1.prepare();
      System.out.println();

      // Customer-2 wants VanillaIceCream + Oreo topping
      System.out.println("Customer-2");
      IceCream iceCream2 = new OreoDecorator(new VanillaIceCream());
      iceCream2.prepare();
      System.out.println(); 

      // Customer-3 wants VanillaIceCream + Oreo + Dry Fruit topping
      System.out.println("Customer-3");
      IceCream iceCream3 = new OreoDecorator(
                    new DryFruitDecorator(new VanillaIceCream()));
      iceCream3.prepare();
      System.out.println(); 

      // Customer-4 wants
      // ChocolateIceCream + Cookie Dough + Dry Fruit + Oreo topping
      System.out.println("Customer-4");
      IceCream iceCream4 = new OreoDecorator(new DryFruitDecorator(
            new CookieDoughDecorator(new ChocolateIceCream())));
      iceCream4.prepare();
      System.out.println(); 

      // Customer-5 wants Cookie Dough two times as
      // StrawberryIceCream + Cookie Dough + Cookie Dough + Dry Fruit
      System.out.println("Customer-5");
      IceCream iceCream5 = new DryFruitDecorator(
            new CookieDoughDecorator(new CookieDoughDecorator(
            new ChocolateIceCream())));
      iceCream5.prepare();
      System.out.println();
   }
}

Output:-

Customer-1
Preparing Vanilla Ice Cream

Customer-2
Preparing Vanilla Ice Cream
Adding Oreo

Customer-3
Preparing Vanilla Ice Cream
Adding DryFruit
Adding Oreo

Customer-4
Preparing Chocolate Ice Cream
Adding CookieDough
Adding DryFruit
Adding Oreo

Customer-5
Preparing Chocolate Ice Cream
Adding CookieDough
Adding CookieDough
Adding DryFruit

See code at GitHub:- Decorator Design Pattern-Example.

Use Cases of Decorator Design Pattern in Java

Some of the use cases of Wrapper or Decorator Design Pattern in Java are:-

  1. BuffereReader br = new BufferedReader(new InputStreamReader(System.in));
  2. DataInputStream dis = new DataInputStream(new FileInputStream("file.txt"));
  3. Scanner scan = new Scanner(System.in);
  4. ObjectInputStream ois = new ObjectInputStream(new FileInputStream("file.txt"));

The complete Java Input/Output stream is designed based on Wrapper or Decorator design pattern. In that,

  • Component:- InputStream, OutputStream, Reader, Writer
  • Concrete Component class:- FileInputStream, FileOutputStream, FileReader, FileWriter
  • Abstract Decoractor:- FilterInputStream, FilterOutputStream
  • Concrete Decorator classes:- DataInputStream, DataOutputStream, BufferedReader, BufferedWriter, and e.t.c.

Decorator design patterns are most frequently used for applying single responsibility principles since we divide the functionality into classes with unique areas of concern. The decorator design pattern is structurally almost like the chain of responsibility pattern.

If you enjoyed this post, share it with your friends. Do you want to share more information about the topic discussed above or do you find anything incorrect? Let us know in the comments. Thank you!

Leave a Comment

Your email address will not be published.