Decorator Design Pattern in Java
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 include
draw()
,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
andRectangle
. But, we would like to have some additional functionalities for theShape
, likeFill-Color
,Line-Color
,Line-Thinkness
,Line-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, concreteShape
classes —Circle
andRectanagle
— for this case.Create enums for
Color
andLineStyle
for shapes. Below is the enum forColor
:
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 Shape
, Circle
, 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.
- Null Object Pattern in Java
-
Using the Adapter Design Pattern in Java
-
Using the Bridge Design Pattern in Java
-
Strategy vs. Factory Design Patterns in Java
-
Decorator Design Pattern in Java
-
How to Use Singleton Design Pattern in Java
-
Singleton Design Pattern: Making Singleton More Effective in Java
Some additional Articles:
-
Java Enums: How to Make Enums More Useful
-
Java Enums: How to Use Configurable Sorting Fields