Overview
The Decorator design pattern is a structural pattern that allows you to add functionality to an object dynamically without changing its original structure. This is achieved by wrapping objects around other objects, often multiple times. Usage of this pattern allows you to write the code like this:
var filteredImage = new SaturationFilter(
new ContrastFilter(
new BrightnessFilter(
image
)
)
);
Using the above structure, you gain the flexibility of being able to easily change which objects are wrapped around each other and in what configuration.
We can visualize this pattern in the following way:
This pattern involves creating a decorator class that wraps around the original object and adds new behaviors or functionalities. This pattern offers better flexibility and extendibility compared to implementing the same functionality using inheritance. The main idea behind this pattern is to expose expected behaviors using a same interface that is implemented by the concrete component and the decorating component. The concrete component delivers some specific functionality, while the decorating components allow the wrapping of the original object, often multiple times. Each additional wrapper adds additional behavior. The client depends on the uniform interface and does not know the implementation details of how objects are chained together. When functionality needs to be extended, objects are chained differently. However, the objects structure or the way that the client interacts with objects does not need to change. That way, the Decorator design pattern offers flexibility and extendibility.
Use Cases
The decorator design pattern is a good fit whenever you need a flexible structure of chaining together related actions in different configurations. Example use cases:
- Image processing: Ability to reach flexibility on adding new filters, such as brightness, contrast, or saturation, to images, without modifying object structure. Each additional filter is a decorator which adds image processing functionality. The client interacts with the uniform interface. Details on the exact filters that are used are hidden. When filters change, the client does not need to change.
- File services with added functionality: Ability to implement flexible objects structure that can save, compress and encrypt data. In some cases, we want to save the plain text file, in some cases only compressed files, and in some cases, compressed and encrypted files. The decorator design pattern is used to chain file content modifiers which can be chained differently without changing the client.
- Email service: Adding encryption, digital signatures, or compression to emails sent by the system without modifying the original object structure.
- Document processing: Adding headers, footers, page numbers, or watermarks to a document without changing the original object structure.
- Reporting system: Adding new formatting options, such as font size, color, or style, to reports generated by the system, without altering the original object structure.
Structure
The Decorator pattern consists of four main components:
- Component: An interface defining the common operations for the original object and its decorators. The client will depend on this interface and will use exposed operations through it.
- Concrete Component: A class that represents the original object that we want to decorate.
- Base Decorator: An abstract class that wraps around the original object. It delegates operations to the wrapped object. It looks at the wrapped object through a uniform interface. That way, it can delegate to the final Component or next Decorator.
- Concrete Decorator: A class that extends the Decorator class and adds specific functionalities to the original object.
Here is a UML diagram that shows the structure of the Decorator pattern:
Example Code
In the below example, we will implement the Image Processing use case using the Decorator Design Pattern.
In this example, we will have the following components:
- Component: the uniform interface will be implemented by
Image
interface. - Concrete Component: will be implemented by
FileImage
. - Base Decorator: will be implemented by
ImageFilter
. - Concrete Decorator: will be implemented by
BrightnessFilter
,ContrastFilter
,SaturationFilter
.
First, let’s implement the Component interface represented by Image
interface:
public interface Image {
void display();
}
Then, let’s implement the Concrete Component represented by FileImage
:
public class FileImage implements Image {
private final String fileName;
public FileImage(String fileName) {
this.fileName = fileName;
}
@Override
public void display() {
System.out.println("Displaying " + fileName);
}
}
Now, let’s implement the Base Decorator represented by ImageFilter
:
abstract class ImageFilter implements Image {
protected Image image;
public ImageFilter(Image image) {
this.image = image;
}
@Override
public void display() {
image.display();
}
}
Finally, we will implement Concrete Decorators:
public class BrightnessFilter extends ImageFilter {
public BrightnessFilter(Image image) {
super(image);
}
@Override
public void display() {
System.out.println("Applying brightness filter");
super.display();
}
}
public class ContrastFilter extends ImageFilter {
public ContrastFilter(Image image) {
super(image);
}
@Override
public void display() {
System.out.println("Applying contrast filter");
super.display();
}
}
public class SaturationFilter extends ImageFilter {
public SaturationFilter(Image image) {
super(image);
}
@Override
public void display() {
System.out.println("Applying saturation filter");
super.display();
}
}
We can use the above code in the following way:
Image image = new FileImage("image.jpg");
Image filteredImage1 = new SaturationFilter(
new ContrastFilter(
new BrightnessFilter(image)
)
);
Image filteredImage2 = new ContrastFilter(
new BrightnessFilter(image)
);
filteredImage1.display();
filteredImage2.display();
Which will produce:
Applying saturation filter
Applying contrast filter
Applying brightness filter
Displaying image.jpg
Applying contrast filter
Applying brightness filter
Displaying image.jpg
Notice how easy it is to change filters applied when the image is displayed. Since the Client depends on the uniform interface, which under the hood can be implemented by the concrete image or filters that delegate to other filters, we can easily implement different use cases like:
- Displaying image without any filters.
- Displaying image with brightness filter.
- Displaying image with contrast filter.
- Displaying image with brightness and saturation filter.
- Displaying image with brightness and contrast filter.
- Displaying image with brightness, contrast, and saturation filter.
For each use case, we only need to change how filters are chained.
You can find the source code for this example on GitHub.
Summary
The Decorator design pattern is a way of adding functionality to an object without changing its original structure. It involves creating a decorator class that wraps around the original object and adds new behaviors. The main concept is to have a uniform interface that is implemented by the concrete component and the decorator components. The concrete component delivers specific functionality, while the decorator components wrap the original object, often multiple times, and add additional behavior. The client depends on the uniform interface and does not need to know the implementation details. When functionality needs to be extended, objects are chained differently, but the object structure and the way the client interacts with objects does not need to change. The main benefit of this pattern is flexibility and extendibility.
References
- Erich G, Richard H, Ralph J, John V. Decorator. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley Professional. October 31, 1994.
- Decorator Design Pattern. Refactoring Guru. Accessed April 14, 2023. https://refactoring.guru/design-patterns/decorator
- Decorator Design Pattern. Sourcemaking. Accessed April 14, 2023. https://sourcemaking.com/design_patterns/decorator