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

Decorator Design Pattern in Java

DZone 's Guide to

Decorator Design Pattern in Java

Want to learn more about using the decorator design pattern in Java? Check out this example using the Shape class to learn how.

· Java Zone ·
Free Resource

Today, I am discussing one of the well-known and frequently used patterns called the decorator design pattern.

Decorator Design Pattern

The decorator design pattern allows us to dynamically add functionality and behavior to an object without affecting the behavior of other existing objects in the same class. 

We use inheritance to extend the behavior of the class. This takes place at compile time, and all of the instances of that class get the extended behavior.

Decorator design patterns allow us to add functionality to an object (not the class) at runtime, and we can apply this customized functionality to an individual object based on our requirement and choice.

  • Decorator patterns allow a user to add new functionality to an existing object without altering its structure. So, there is no change to the original class.

  • The decorator design pattern is a structural pattern, which provides a wrapper to the existing class.

  • Decorator design pattern uses abstract classes or interfaces with the composition to implement the wrapper.

  • Decorator design patterns create decorator classes, which wrap the original class and provide additional functionality by keeping the class methods' signature unchanged.

  • Decorator design patterns are most often used for applying single responsibility principles since we divide the functionality into classes with unique areas of concern.

  • The decorator design pattern is structurally similar to the chain of responsibility pattern.

Let's take a look at an example to better understand the pattern.

Steps to Implementing the Decorator Design Pattern

  • Suppose, we have a Shape interface, which can includedraw()resize() ,  isHide(), anddescription().

package design.decorator;

public interface Shape {
    void draw();
    void resize();
    String description();
    boolean isHide();
}


  • Now, we have two concrete classes of Shape  — Circle and Rectangle — to define a specific shape.

Below is the code for Circle: 

package design.decorator;

public class Circle implements Shape {

    @Override
    public void draw() {
    System.out.println("Drawing Circle");
    }

    @Override
    public void resize() {
    System.out.println("Resizing Circle");
    }

    @Override
    public String description() {
    return "Circle object";
    }

    @Override
    public boolean isHide() {
    return false;
    }

}


Below is the code forRectangle:

package design.decorator;

public class Rectangle implements Shape {

    @Override
    public void draw() {
    System.out.println("Drawing Rectangle");
    }

    @Override
    public void resize() {
    System.out.println("Resizing Rectangle");
    }

    @Override
    public String description() {
    return "Rectangle object";
    }

    @Override
    public boolean isHide() {
    return false;
    }

}


  • Now, let's take a look at the decoration portion. So far, all is good, and we can draw Circle and Rectangle. But, we would like to have some additional functionalities for the Shape, like Fill-ColorLine-Color,  Line-ThinknessLine-Style, and so on.

  • First, we will create an abstract wrapper (decorator) class that implements the Shape. I will use theShapeDecorator for this example.

package design.decorator;

public abstract class ShapeDecorator implements Shape {
      protected Shape decoratedShape;

      public ShapeDecorator(Shape decoratedShape) {
            super();
            this.decoratedShape = decoratedShape;
      }

}


  • I kept this abstract to avoid any direct instantiation since this is just a wrapper and does not add any functionality into the shape. Also, I have implemented Shape to allow adding additional functionalities to all of the defined, concrete Shape classes — Circle andRectanagle — for this case.

  • Create enums for  Color and LineStyle for shapes. Below is the enum for  Color:

package design.decorator;

public enum Color {
      RED,
      GREEN,
      BLUE,
      YELLOW,
      WHITE,
      BLACK,
      ORANGE,
      MAROON
}


  • Below is the enum for  LineStyle:

package design.decorator;

public enum LineStyle {
      SOLID,
      DASH,
      DOT,
      DOUBLE_DASH,
      DASH_SPACE
}


  • Create the FillColorDecorator to add the functionality of the fill-color in the shape.

package design.decorator;

public class FillColorDecorator extends ShapeDecorator {

      protected Color color;

      public FillColorDecorator(Shape decoratedShape, Color color) {
            super(decoratedShape);
            this.color = color;
      }

      @Override
      public void draw() {
            decoratedShape.draw();
            System.out.println("Fill Color: " + color);
      }

      // no change in the functionality
      // we can add in the functionality if we like. there is no restriction
      // except we need to maintain the structure of the Shape APIs
      @Override
      public void resize() {
      decoratedShape.resize();
      }

      @Override
      public String description() {
      return decoratedShape.description() + " filled with " + color + " color.";
      }

      // no change in the functionality
      @Override
      public boolean isHide() {
      return decoratedShape.isHide();
      }

}


  • Create the  LineColorDecorator to add the functionality of line-color in the shape.

package design.decorator;

public class LineColorDecorator extends ShapeDecorator {

      protected Color color;

      public LineColorDecorator(Shape decoratedShape, Color color) {
      super(decoratedShape);
      this.color = color;
      }

      @Override
      public void draw() {
      decoratedShape.draw();
      System.out.println("Line Color: " + color);
      }

      // no change in the functionality
      @Override
      public void resize() {
      decoratedShape.resize();
      }

      @Override
      public String description() {
      return decoratedShape.description() + " drawn with " + color + " color.";
      }

      // no change in the functionality
      @Override
      public boolean isHide() {
      return decoratedShape.isHide();
      }

}


  • Create the  LineThicknessDecorator to add the functionality of the custom line thickness in the shape.

package design.decorator;

public class LineThinknessDecorator extends ShapeDecorator {

      protected double thickness;

      public LineThinknessDecorator(Shape decoratedShape, double thickness) {
                super(decoratedShape);
                this.thickness = thickness;
      }

      @Override
      public void draw() {
                decoratedShape.draw();
                System.out.println("Line thickness: " + thickness);
      }

      // no change in the functionality
      @Override
      public void resize() {
      decoratedShape.resize();
      }

      @Override
      public String description() {
      return decoratedShape.description() + " drawn with line thickness " + thickness + ".";
      }

      // no change in the functionality
      @Override
      public boolean isHide() {
      return decoratedShape.isHide();
      }

}
  • Create LineStyleDecorator to add functionality of custom line styles in the shape.

package design.decorator;

public class LineStyleDecorator extends ShapeDecorator {

        protected LineStyle style;

        public LineStyleDecorator(Shape decoratedShape, LineStyle style) {
        super(decoratedShape);
        this.style = style;
        }

        @Override
        public void draw() {
        decoratedShape.draw();
        System.out.println("Line Style: " + style);
        }

        // no change in the functionality
        @Override
        public void resize() {
        decoratedShape.resize();
        }

        @Override
        public String description() {
        return decoratedShape.description() + " drawn with " + style + " lines.";
        }

        // no change in the functionality
        @Override
        public boolean isHide() {
        return decoratedShape.isHide();
        }

}


  • Create a sample Main program to execute and test the decorator code.

package design.decorator;

public class Main {

  public static void main(String[] args) {

            System.out.println("Creating Simple Shape Objects...");
            Shape rectangle = new Rectangle();
            Shape circle = new Circle();

            System.out.println("Drawing Simple Shape Objects...");
            rectangle.draw();
            System.out.println();
            circle.draw();
            System.out.println();

            System.out.println("Creating Decorated Circle with Red Color, Blue Lines in dash pattern and thickness of 2 ...");
            Shape circle1 = new FillColorDecorator(new LineColorDecorator(new LineStyleDecorator(
            new LineThinknessDecorator(new Circle(), 2.0d), LineStyle.DASH), Color.BLUE), Color.RED);
            circle1.draw();
            System.out.println();
            // order of decorator is also not much important here since all are unique functionalities.
            // we can also do this nesting of functionalities in separate statements.
            System.out.println("creating object with similar functionalities in separate statements.");
            Circle c = new Circle();
            LineThinknessDecorator lt = new LineThinknessDecorator(c, 2.0d);
            LineStyleDecorator ls = new LineStyleDecorator(lt, LineStyle.DASH);
            LineColorDecorator lc = new LineColorDecorator(ls, Color.BLUE);
            FillColorDecorator fc = new FillColorDecorator(lc, Color.RED);
            Shape circle3 = fc;
            circle3.draw();
            System.out.println();

            System.out.println("Creating Decorated Circle with Green Color, Black Lines ...");
            Shape circle2 = new FillColorDecorator(new LineColorDecorator(new Circle(), Color.BLACK), Color.GREEN);
            circle2.draw();
            System.out.println();

            System.out.println("Creating Decorated Rectange with Yellow Color, Red Lines in double dash pattern...");
            Shape rectangle1 = new FillColorDecorator(new LineColorDecorator(new Rectangle(), Color.RED), Color.YELLOW);
            rectangle1.draw();
            System.out.println();

      }

}


  • Below is the output of the code:

Creating Simple Shape Objects...
Drawing Simple Shape Objects...
Drawing Rectangle

Drawing Circle

Creating Decorated Circle with Red Color, Blue Lines in dash pattern and thickness of 2 ...
Drawing Circle
Line thickness: 2.0
Line Style: DASH
Line Color: BLUE
Fill Color: RED

creating object with similar functionalities in separate statements.
Drawing Circle
Line thickness: 2.0
Line Style: DASH
Line Color: BLUE
Fill Color: RED

Creating Decorated Circle with Green Color, Black Lines ...
Drawing Circle
Line Color: BLACK
Fill Color: GREEN

Creating Decorated Rectange with Yellow Color, Red Lines in double dash pattern...
Drawing Rectangle
Line Color: RED
Fill Color: YELLOW


As we can see, we have not changed the Core classes, Shape , Circle, and  Rectangle. By creating the wrapper and decorator classes, we have added and customized the behavior of ShapeCircle, and Rectangle. There you have it. I hope we are clear on the decorator design pattern now.

Liked the article? Don't forget to press that like button. Happy coding!

Need more articles on Design Patterns? Below are some of them I have shared with you.

Some additional Articles:

Topics:
java ,decorator design pattern ,tutorial ,design patterns

Opinions expressed by DZone contributors are their own.

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

{{ parent.tldr }}

{{ parent.urlSource.name }}