A decorator design pattern allows dynamically attaching the additional responsibilities or behaviors to an object at runtime. It is a structural pattern and makes use of aggregation to combine those behaviors.
In this tutorial, we’ll learn to implement the decorator pattern.
Let’s start by looking at the UML representation of a decorator pattern:
The ConcreteComponent class is the one for which we’ll like add additional behaviors at runtime. The ConcreteDecorator1, ConcreteDecorator2, … are the decorator classes which hold the logic to decorate a given Component.
Note that the abstract Decorator class ‘has a‘ Component. In other words, it aggregates any other type of component which allows us to stack components one on the top of the other.
Moreover, both the ConcreteComponent and Decorator classes implement a common interface – Component.
Let’s say we are selling a gift item. Once a user selects a gift item, there can be multiple ways just to decorate that gift item say with a red or a blue ribbon, purple or green gift wrap, etc.
Rather than creating a class for each possible combination, it’s a good idea to implement it using a decorator pattern.
So, let’s create our GiftComponent interface:
public interface GiftComponent { void pack(); }
public class GiftItem implements GiftComponent { public void pack() { System.out.println("Putting it in a box"); } }
Now that we have a GiftItem that we’ll like to decorate, let’s define our abstract GiftDecorator class:
public abstract AbstractGiftDecorator implements GiftComponent { protected GiftComponent gift; public AbstractGiftDecorator(GiftComponent gift) { this.gift = gift; } public void pack() { this.gift.pack(); } }
Finally, we can create as many custom decorators as we want.
Let’s create a few gift wraps:
public class PurpleWrapper extends AbstractGiftDecorator { public PurpleWrapper(GiftComponent gift) { super(gift); } public void pack() { super.pack(); System.out.println("Purple wrapper"); } } public class RedWrapper extends AbstractGiftDecorator { public RedWrapper(GiftComponent gift) { super(gift); } public void pack() { super.pack(); System.out.println("Red wrapper"); } }
public class BlueRibbon extends AbstractDecorator { public BlueRibbon(GiftComponent gift) { super(gift); } public void pack() { super.pack(); System.out.println("Blue ribbon"); } } public class PinkRibbon extends AbstractDecorator { public PinkRibbon(GiftComponent gift) { super(gift); } public void pack() { super.pack(); System.out.println("Pink Ribbon"); } }
Let’s now test out our implementation to see what happens:
// client code GiftComponent gift = new GiftItem(); GiftComponent giftWithPurpleWrapper = new PurpleWrapper(gift); GiftComponent giftWithPurpleWrapperAndPinkRibbon = new PinkRibbon(giftWithPurpleWrapper); giftWithPurpleWrapperAndPinkRibbon.pack();
Putting it in a box Purple Wrapper Pink Ribbon
The decorator design pattern uses aggregation as a substitute for a pure inheritance. It allows us to dynamically add behaviors to an object. It takes away the overhead of creating a separate class for every possible combination thereby significantly reducing the number of classes.
Also, it adheres to the Single Responsibility Principle which states that every class must exactly do one thing. The classes like java.io.BufferedReader, java.io.FileReader are designed using the decorator design pattern.