Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Decorator Design Patterns

DZone's Guide to

Decorator Design Patterns

Want to learn more about object-oriented programming and the decorator design patterns? Check out this tutorial to learn more!

· Java Zone ·
Free Resource

Download Microservices for Java Developers: A hands-on introduction to frameworks and containers. Brought to you in partnership with Red Hat.

In an 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 an inheritance. However, there is a difference between the two. 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 patterns simply allow you to dynamically add functionality to a specific object instead of an entire class of objects.

The best thing is that we can bind one object with the decorator and wrap their result with another decorator.

Why Decorators?

Let us assume that we are building a simple design to keep the data and model of the available mobile phones in the market. Here, we have a 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 combinations in specs and the model.

Here is the catch — we cannot 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 patterns have 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) to implement 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 that implements the Phoneinterface, 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();
    }
}


Here is the 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 the phone object can behave dynamically, rendering property of the BasicPhone in the first case. Here, phone = new AndroidPhone(new BasicPhone()) renders properties of the BasicPhone first and, then, the Android Phone. However, the same phone, when passed to new IPhone(phone), shows properties of BasicPhone and AndroidPhone, followed by the iPhone. Essentially, 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. 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 they are 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!

Happy Coding!

Download Building Reactive Microservices in Java: Asynchronous and Event-Based Application Design. Brought to you in partnership with Red Hat

Topics:
design pattern - structural ,java ,software development ,decorator design pattern ,object oriented programming ,tutorial

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}