Decorator Design Patterns

In object oriented paradigm, Decorators are one of the largely used Structural Design Patterns. This pattern changes the functionality of objects at runtime without impacting its blueprint or the existing functionality.

While working with these patterns, we might confuse decorators with Inheritance. However, there exists a difference. Decorators are used when we have to add and remove responsibilities from an object dynamically. Whereas, inheritance can do the same, but not at run time.

Decorator Pattern simply allows you to dynamically add functionality to specific object instead of an entire class of objects.

The best thing is : we can bind one object with the decorator, and then wrap their result with another decorator, and we can go on.

Why Decorators ?

Let us assume we are building a simple design to keep the data and model of the available mobile phones in the market. Here, we have number of mobile phones to take into account with various models and overlapping specs. How can we implement the design?

One way to do that is by using the traditional inheritance. We will create a base Phone class and multiple subclasses for different phone combination in specs and model.

Here is the catch, we can not create n number of mobile phones through inheritance, especially when we know specs will overlap for multiple phones. We need to think of something better. Decorator design pattern is the answer.

Implementation

Suppose we want to have different types of mobiles phones.

Let us create a Phone interface and BasicPhone class (having basic features ) implementing it.

public interface Phone {

public void printModel();

}
public class BasicPhone implements Phone {

@Override
public void printModel() {
System.out.println("Basic Phone");;
}

}

Now we will create a PhoneDecorator class which implements Phone interface having one Phone object as its property.

public class PhoneDecorator implements Phone {

public Phone phone;

public PhoneDecorator(Phone phone) {
this.phone = phone;
}

@Override
public void printModel() {
this.phone.printModel();
}

}

It has a HAS-A relationship with the interface Phone as the Phone object must be accessible to all the child decorators.

Now, let us create two more classes extending PhoneDecorator, say AndroidPhone and IPhone.

public class AndroidPhone extends PhoneDecorator {

public AndroidPhone(Phone phone) {
super(phone);
}

@Override
public void printModel() {
super.printModel();
System.out.println("Adding Features of Android");
}

}

Similarily,

public class IPhone extends PhoneDecorator {

public IPhone(Phone phone) {
super(phone);
}

@Override
public void printModel() {
super.printModel();
System.out.println("Adding Features of iPhone");
}
}

We are almost done! Let us checkout the class diagram.

Now let’s test it!

public class Test {

public static void main(String[] args) {
System.out.println("Test 1\n");
Phone phone = new AndroidPhone(new BasicPhone());
phone.printModel();
System.out.println("\nTest 2\n");
Phone phone1 = new IPhone(phone);
phone1.printModel();
}
}

Output!

Test 1
Basic Phone
Adding Features of Android
Test 2
Basic Phone
Adding Features of Android
Adding Features of iPhone

Here we can see how phone object can behave dynamically rendering property of BasicPhone in the first case. Here, phone = new AndroidPhone(new BasicPhone()) renders properties of Basic Phone first, and then Android Phone. However, the same phone when passed to new IPhone(phone) as well, then it shows properties of BasicPhone, AndroidPhone, followed by IPhone. (Though Apple will not allow that, :P). So practically, you can write wrappers over wrappers without touching the basic class blueprint.

Conclusion

Now we know that Decorators are used when we dynamically assign the behaviours to objects without messing up the code that uses these objects. Though, inheritance also does the same, but in a static manner.

Wearing clothes is an example of using decorators. When we are cold, you wrap ourself with a sweater. If it is still cold, we can wear a jacket on top. If it is raining, we can put on a raincoat.

All of these are not part of us. Therefore we can easily remove them whenever not needed. One of the applications of Decorators can be found in java.io package (1 & 2). For example :

BufferedInputStream bis=new BufferedInputStream(new FileInputStream(new File("abc.txt")));

Though decorators have their own cons like every other good thing such as presence of lots of small classes and problems in configuring multi-wrapped properties, these are good to know because of their ability to compose complex objects into simple ones instead of having monolithic, single point agenda classes. If used intelligently, they might come handy in your next LLD!

Thanks.