Decorator Design Pattern in Java

Decorator Design Pattern in Java with Examples

In this article, I am going to discuss Decorator Design Pattern in Java with Examples. Please read our previous article where we discussed Composite Design Pattern in Java with Examples. The Decorator Design Pattern falls under the category of the Structural Design Pattern. In this article, we will explore the fundamental principles, advantages, and potential disadvantages of the Decorator Design Pattern in Java, highlighting its significance in achieving code reusability, flexibility, and maintainability.

What is a Decorator Design Pattern?

In software development, the need to add additional behaviors or modify the functionality of objects dynamically is a common requirement. The Decorator design pattern offers an elegant solution by providing a flexible and modular way to extend the functionality of an object at runtime. By wrapping the object in multiple layers of decorators, each layer adding a specific behavior, the pattern allows for incremental enhancements without modifying the original object’s structure.

The Decorator Design Pattern in Java allows us to dynamically add new functionalities to an existing object without altering or modifying its structure and this design pattern acts as a wrapper to the existing class. That means Decorator Design Pattern dynamically changes the functionality of an object at runtime without impacting the existing functionality of the object.

Real-Time Example of Decorator Design Pattern:

Let us understand the Decorator Design Pattern with one Real-Time Example. Please have a look at the following image. On the left-hand side, you can see the Car without any engine. Let’s say, I want to add either a Petrol engine or a Diesel engine to this car. Then what I need to do is, I have to introduce the CarDecorator. What this CarDecorator will do is, it will add an Engine to the Car. Let’s say I want to add a Petrol Engine then the CarDecorator will add the Petrol Engine to this car and return the car with a petrol engine to the client. Suppose, I want to add a Diesel Engine to this car, then the CarDecorator will add a Diesel engine to this car and return the car with the Diesel engine to the client.

Real-Time Example of Decorator Design Pattern

As per the Decorator Design Pattern, the work of the decorator is to add new functionalities or behavior to an existing object without altering its structure. So, in this case, a Car without an engine is the existing object. What the CarDecorator does is, it will add a Petrol Engine or a Diesel Engine to the Car based on the requirement of the client. So, I think this is one of the best real-time examples of the Decorator Design Pattern.

UML or Class Diagram of Decorator Design Pattern:

The Decorator design pattern is a structural pattern that enables the addition of new behaviors to an object dynamically. It involves the following components:

UML or Class Diagram of Decorator Design Pattern

Let us understand each component in detail.

  • Component: Defines the interface for the original object and the decorators. It provides the common operations that decorators and the original object must implement.
  • Concrete Component: This represents the original object to which additional functionality can be added. It implements the operations defined in the component interface.
  • Decorator: Maintains a reference to the component and implements the component interface. It acts as a wrapper and adds new behaviors before or after delegating to the component’s operations.
  • Concrete Decorator: Extends the functionality of the decorator by adding specific behaviors. It adds new operations or modifies the behavior of existing operations, enhancing the functionality of the component.
Example to Understand Decorator Design Pattern in Java

Let’s consider a real-world example where the Decorator pattern can be applied: a pizza ordering system. In this scenario, customers can order different types of pizza, such as veg, chicken, or fish.

The Decorator pattern can be used to implement the customization options for the pizza orders. The base Pizza interface will define the common behavior of all pizza types. Concrete classes, such as VegPizza, ChickenPizza, and FishPizza, will implement the Pizza interface and provide the basic functionality.

To add customization options, decorators will be created. Each decorator class will implement the PIzza interface and wrap an instance of a concrete pizza class. Decorators can add additional behavior, such as to the base pizza. Multiple decorators can be stacked to create customized combinations.

By using the Decorator pattern, the pizza ordering system can achieve several benefits. Firstly, it allows for dynamic customization of pizza orders at runtime. Customers can choose their desired toppings and flavors, and decorators will add the corresponding behavior to the pizza. Secondly, it promotes code flexibility and extensibility, as new decorators can be easily added without modifying existing pizza classes. Additionally, the pattern supports code reusability, as decorators can be shared among different types of pizza. The UML Diagram of this example is given below using Decorator Design Pattern.

Example to Understand Decorator Design Pattern in Java

Implementing Decorator Design Pattern in Java

Step 1: Create a new directory to store all the class files of this project.

Step 2: Open VS Code and create a new project, called decorator.

Step 3: In the project, create a new file called Pizza.java. Add the following code to the file:

Implementing Decorator Design Pattern in Java

Step 4: In the project, create a new file called VegPizza.java. Add the following code to the file:

Decorator Design Pattern in Java

Step 5: In the project, create a new file called PizzaDecorator.java. Add the following code to the file:

Decorator Design Pattern in Java with Examples

Step 6: In the project, create a new file called ChickenPizza.java. Add the following code to the file:

Decorator Pattern in Java

Step 7: In the project, create a new file called FishPizza.java. Add the following code to the file:

Decorator Pattern in Java with Examples

Step 8: In the project, create a new file called DecoratorPatternDemo.java. This class will contain the main() function.

Step 9: Import the following packages into DecoratorPatternDemo.java:

What is a Decorator Design Pattern?

Step 10: Write the main() function in DecoratorPatternDemo.java:

Example to Understand Decorator Design Pattern in Java

In the main function, the user is asked for their choice of pizza. Based on this user input, we “make” the appropriate pizza and print out its price to the user.

Step 11: Compile and execute the application. Ensure compilation is successful. Verify that the program works as expected.

Example to Understand Decorator Design Pattern

Congratulations! You now know how to implement decorator patterns!

UML Diagram of Decorator Design Pattern:

Now, let us see the Decorator Design Pattern UML Diagram Components with our Example so that you can easily understand the UML Diagram.

UML Diagram of Decorator Design Pattern

The classes can be described as follows:

  1. Object: This class represents the basic interface that defines the basics of a pizza.
  2. ObjectDecorator: This class represents the decorator class. This class is also an interface and it extends from the Object interface. This defines some more functions, such as toppings.
  3. ConcreteObject: This represents the concrete object that implements all of the functions defined in the aforementioned interfaces.
  4. DriverClass: This class contains the main() function and is responsible for the simulation of the program.
The Complete Example Code of Decorator Design Pattern in Java
ChickenPizza.java
public class ChickenPizza extends PizzaDecorator
{
    public ChickenPizza(Pizza p)    {super(p);}
    public String makePizza()       {return super.makePizza() + " with chicken strips";}
    public int getPrice()           {return super.getPrice() + 100;}
}
DecoratorPatternDemo.java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class DecoratorPatternDemo
{
    public static void main(String[] args) throws IOException
    {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        System.out.println();
        System.out.println("Select pizza: ");
        System.out.println("1. Veg Pizza");
        System.out.println("2. Chicken Pizza");
        System.out.println("3. Fish Pizza");
        System.out.println();
        System.out.println("Enter your choice here: ");

        int choice = Integer.parseInt(br.readLine());
        
        switch (choice)
        {
            case 1:
                {
                    VegPizza p = new VegPizza();
                    System.out.println(p.makePizza());
                    System.out.println("Price: " + p.getPrice());
                }
                break;
            
            case 2:
                {
                    Pizza p = new ChickenPizza(new VegPizza());
                    System.out.println(p.makePizza());
                    System.out.println("Price: " + p.getPrice());
                }
                break;
            
            case 3:
                {
                    Pizza p = new FishPizza(new VegPizza());
                    System.out.println(p.makePizza());
                    System.out.println("Price: " + p.getPrice());
                }
                break;

            default:
                System.out.println("Wrong choice!");
        }
    }    
}
FishPizza.java
public class FishPizza extends PizzaDecorator
{
    public FishPizza(Pizza p)    {super(p);}
    public String makePizza()    {return super.makePizza() + " with shrimp and scallops";}
    public int getPrice()        {return super.getPrice() + 150;}
}
Pizza.java
public interface Pizza
{
    public String makePizza();
    public int getPrice();
}
PizzaDecorator.java
public abstract class PizzaDecorator implements Pizza
{
    private Pizza p;

    public PizzaDecorator(Pizza p)  {this.p = p;}

    @Override
    public String makePizza()       {return p.makePizza();}

    @Override
    public int getPrice()           {return p.getPrice();}
}
VegPizza.java
public class VegPizza implements Pizza
{
    @Override
    public String makePizza()   {return "Veg Pizza";}

    @Override
    public int getPrice()       {return 100;}
}
Advantages of Decorator Design Pattern in Java:

Some of the Advantages of using the Decorator Design Pattern in Java are as follows:

  • Enhanced Flexibility: The Decorator pattern offers flexibility in extending the functionality of an object at runtime. By dynamically adding or removing decorators, developers can modify the behavior of objects without changing their underlying structure. This flexibility enables fine-grained control over the object’s functionality, allowing for customized behavior combinations.
  • Code Reusability: The Decorator pattern promotes code reusability by allowing decorators to be used interchangeably. Decorators can be stacked in various combinations to add different behaviors to the original object. This reusability reduces code duplication and promotes a modular design, as decorators can be independently developed and tested.
  • Open-Closed Principle: The Decorator pattern adheres to the Open-Closed Principle, as it allows for extending the functionality of objects without modifying their existing code. New decorators can be added without impacting the client code that uses the original component. This promotes code stability and minimizes the risk of introducing bugs in existing functionality.
  • Easy Composition of Behaviors: The Decorator pattern enables the easy composition of multiple behaviors. Decorators can be stacked in a layered manner, with each decorator adding a specific behavior. This composition allows for the creation of complex combinations of behaviors by simply adding or removing decorators, providing great flexibility in functionality design.
  • Simplified Maintenance: The Decorator pattern simplifies maintenance by separating concerns into individual decorators. Each decorator focuses on a specific behavior, making it easier to understand, modify, or extend a particular aspect of functionality. This modular approach enhances code maintainability and reduces the impact of changes on other parts of the system.
Disadvantages of Decorator Design Pattern in Java:

Some of the Disadvantages of using the Decorator Design Pattern in Java are as follows:

  • Increased Complexity: The Decorator pattern can introduce additional complexity to the codebase. The stacking of decorators and the delegation of operations can lead to intricate interactions and potential performance overhead. Developers must carefully manage the number and order of decorators to avoid excessive complexity and maintain acceptable system performance.
  • Large Number of Classes: Implementing the Decorator pattern may result in a significant number of classes, especially in scenarios with a wide range of possible behaviors. Each decorator and combination of decorators requires its own class, which can increase the overall codebase size and potentially impact system performance and readability.
  • Order Dependency of Decorators: The order in which decorators are applied can be critical in achieving the desired functionality. Changing the order of decorators may lead to different outcomes or unintended side effects. This order dependency can introduce challenges in understanding and maintaining the code, especially in complex scenarios with numerous decorators.
  • Limited Type Checking: Since decorators implement the same interface as the original component, the type checking at compile-time may be limited. This can make it challenging to catch certain errors or enforce specific type-related constraints. Runtime errors related to incompatible decorators or incorrect usage may only be discovered during testing or execution.

In the next article, I am going to discuss Flyweight Design Pattern in Java with Examples. Here, in this article, I try to explain Decorator Design Pattern in Java with Examples. I hope you understood the need for and use of the Decorator Design Pattern in Java.

Leave a Reply

Your email address will not be published. Required fields are marked *