Decorator Pattern in Java and Python
This article exists on YouTube
The entire content of this article is also available on YouTube. It's your choice: if you prefer video content, you can watch it on YouTube. If you prefer reading, feel free to continue here.
What is a Decorator Pattern?
The Decorator Pattern is a programming pattern used in software engineering and is part of the group of structural patterns. It may be used in any object-oriented programming language, such as Java, Python, C++, and so on. What it does is to add behavior to individual objects without affecting the behavior of the objects themselves. The added behavior can be before, after, or before and after the invidiual objects. There are four parts that are necessary to make the Decorator Pattern work.
So what is the Decorator Pattern used for? This pattern is used in various software systems and frameworks. Examples of real-world use include the I/O Streams in Java, the Java Swing GUI Toolkit, the Java Servlet API, Python's Flask and Django Web Frameworks, Java's Collections Class, Logging Frameworks, such as Security Frameworks.
The Decorator Pattern is extensively used within the I/O streams in Java, i.e. attaching additional functionalities to a FileInputStream is done by decorating it with a BufferedInputStream or a DataInputStream.
Decorators are also used within Swing when adding borders, scroll bars, buttons, panels, frames, etc..
The request and response objects in the Java Servlet API can be decorated to provide additional functionality, like compression, encryption, logging, etc..
Within these frameworks, be it Flask for smaller projects, or be it the full-featured framework Django, decorators are used for adding functionality to views, routes, and middleware components.
In Java the Collections class provides various utility methods for working with collections in general. Decorators are used to add specific behavior to collections. Examples are the unmodifiableList where the decorator is used to decorate the original collection with the behavior of being unmodifiable, and the synchronizedList making use of a decorator to synchronize access to the underlying collection to ensure thread safety when multiple threads access or modify the collection concurrently.
The logging frameworks Log4J or SLF4J allow to decorate loggers with additional functionality such as formatting, filtering, or routing log messages to certain destinations.
In general, security frameworks might use decorators to add i.e. authentication, authorization, encryption, validation, etc..
What is the structure of a Decorator Pattern?
-
Abstract Component
This base interface or abstract class defines the methods to be implemented. -
Concrete Component
This is the implementation of the abstract component which is the object to which additional functionality can be added. -
Abstract Decorator
This abstract class implements the abstract component and also has a reference to a concrete component. -
Concrete Decorator
These are classes that extend the abstract decorator class, adding specific behavior to the components.
Example Codes
There are a number of programming languages where the Decorator Pattern can prove useful. Every language that supports the construct of classes can make use of this pattern.
All the codes within this article can also be found on the GitHub repository programming-patterns and in there within the directory Decorator Pattern.
Java Example
The following example is a Java application putting the Decorator Pattern into action. All the following files need to be placed into the same folder so that the code within the files can work properly, because the files also reference each other. Being the smallest possible code example, this little project consists of six Java files:
- AbstractComponent.java
- ConcreteComponent.java
- AbstractDecorator.java
- ConcreteDecoratorA.java
- ConcreteDecoratorB.java
- Main.java
The first thing that needs to be defined is the abstract component. This is the definition of what methods the class has that will later be decorated.
public interface AbstractComponent {
void operation();
}
The abstract component will have to be implemented at least once to form a concrete component.
public class ConcreteComponent implements AbstractComponent {
@Override
public void operation() {
System.out.println("Operation of the ConcreteComponent.");
}
}
Now we need to define the abstract decorator which acts as a template for the concrete decorator(s).
abstract class AbstractDecorator implements AbstractComponent {
protected AbstractComponent component;
public AbstractDecorator(AbstractComponent component) {
this.component = component;
}
@Override
public void operation() {
this.component.operation();
}
}
For example, we define two concrete decorators, A and B, both extending the abstract decorator we just defined.
public class ConcreteDecoraterA extends AbstractDecorator {
public ConcreteDecoraterA(AbstractComponent component) {
super(component);
}
@Override
public void operation() {
super.operation();
this.addedBehavior();
}
private void addedBehavior() {
System.out.println("Added behavior from ConcreteDecoratorA.");
}
}
public class ConcreteDecoratorB extends AbstractDecorator {
public ConcreteDecoratorB(AbstractComponent component) {
super(component);
}
@Override
public void operation() {
super.operation();
this.addedBehavior();
}
private void addedBehavior() {
System.out.println("Added behavior from ConcreteDecoratorB.");
}
}
And that is it already. The main construct of the Decorator Pattern consists of these six Java files. The only thing that remains is to apply their logic within the executing main class. The main(...) method creates an instance of the ConcreteComponent class. Right after that it creates an instance of the ConcreteDecoratorA class and provides the component variable to the constructor. After that the operation() method is executed, leading to a print to the command line interface. The process is repeated with the ConcreteDecoratorB class, and after that both concrete decorators are instantiated one after another and their result stored in the decoratedComponentAB variable. This leads to a series of command line interface prints.
public class Main {
public static void main(String[] args) {
System.out.println("Decorator Pattern in Java");
System.out.println("-------------------------");
System.out.println();
// Creating a concrete component.
AbstractComponent component = new ConcreteComponent();
System.out.println("Concrete Component:");
component.operation();
System.out.println();
// Decorating the concrete component with the concrete decorator a.
AbstractComponent decoratedCompontentA = new ConcreteDecoraterA(component);
System.out.println("Decorated Component A:");
decoratedCompontentA.operation();
System.out.println();
// Decorating the concrete component with the concrete decorator b.
AbstractComponent decoratedComponentB = new ConcreteDecoratorB(component);
System.out.println("Decorated Component B:");
decoratedComponentB.operation();
System.out.println();
// Decorating the concrete component with both concrete decorators nested.
AbstractComponent decoratedComponentAB = new ConcreteDecoratorB(new ConcreteDecoraterA(component));
System.out.println("Decorated Component AB:");
decoratedComponentAB.operation();
}
}
There also needs to be a Manifest.txt file within the same folder. This is a metadata file used primarily in JAR files. It contains information about the JAR file itself and its contents. It is placed within thie META-INF folder within the JAR archive file structure.
Main-Class: Main
To compile this code into an executable Java application which would be a JAR file, a couple of commands need to be executed from within the same directory. The commands are found within the build file.
@echo off
javac *.java
jar cvfm DecoratorDemo.jar Manifest.txt *.class
#!/bin/bash
javac *.java
jar cvfm DecoratorDemo.jar Manifest.txt *.class
Now that the compilation has taken place, there are new files within the same directory. Every JAVA file has its equivalent CLASS file, and there is also a file called DecoratorDemo.jar. This is the Java application that needs to be executed. The command for that is within the run file.
@echo off
java -jar DecoratorDemo.jar
#!/bin/bash
java -jar DecoratorDemo.jar
Testing the Java Example
Now that all the files are ready and within their correct location they can be put to test. First we need to compile the Java code and then we can execute it to see what it will print in the Windows Command Prompt or in the MacOS / Linux Terminal.
Python Example
The following example is a Python application putting the Decorator Pattern into action. All the following files need to be placed into the same folder so that the code within the files can work properly, because the files also reference each other. As this is the smallest possible example, this little project consists of six Python files:
- abstract_component.py
- concrete_component.py
- abstract_decorator.py
- concrete_decorator_a.py
- concrete_decorator_b.py
- main.py
The first thing that needs to be defined is the abstract component. This is the definition of what methods the class has that will later be decorated.
class AbstractComponent:
def operation(self):
pass
The abstract component will have to be implemented at least once to form a concrete component.
from abstract_component import AbstractComponent
class ConcreteComponent(AbstractComponent):
def operation(self):
print("Operation of the ConcreteComponent.")
Now we need to define the abstract decorator which acts as a template for the concrete decorator(s).
from abstract_component import AbstractComponent
class AbstractDecorator(AbstractComponent):
def __init__(self, component):
self._component = component
def operation(self):
self._component.operation()
For example, we define two concrete decorators, A and B, both extending the abstract decorator we just defined.
from abstract_decorator import AbstractDecorator
class ConcreteDecoratorA(AbstractDecorator):
def operation(self):
super().operation()
self.added_behavior()
def added_behavior(self):
print("Added behavior from ConcreteDecoratorA.")
from abstract_decorator import AbstractDecorator
class ConcreteDecoratorB(AbstractDecorator):
def operation(self):
super().operation()
self.added_behavior()
def added_behavior(self):
print("Added behavior from ConcreteDecoratorB.")
And that is it already. The main construct of the Decorator Pattern consists of these six Python files. The only thing that remains is to apply their logic within the executing main class. The main() method creates an instance of the ConcreteComponent class. Right after that it creates an instance of the ConcreteDecoratorA class and provides the component variable to the constructor. After that the operation() method is executed, leading to a print to the command line interface. The process is repeated with the ConcreteDecoratorB class, and after that both concrete decorators are instantiated one after another and their result stored in the decorated_component_ab variable. This leads to a series of command line interface prints.
from concrete_component import ConcreteComponent
from concrete_decorator_a import ConcreteDecoratorA
from concrete_decorator_b import ConcreteDecoratorB
def main():
print("Decorator Pattern in Python")
print("---------------------------")
print()
# Creating a concrete component.
component = ConcreteComponent()
print("Concrete Component:")
component.operation()
print()
# Decorating the concrete component with the concrete decorator a.
decorated_component_a = ConcreteDecoratorA(component)
print("Decorated Component A:")
decorated_component_a.operation()
print()
# Decorating the concrete component with the concrete decorator b.
decorated_component_b = ConcreteDecoratorB(component)
print("Decorated Component B:")
decorated_component_b.operation()
print()
# Decorating the concrete component with both concrete decorators nested.
decorated_component_ab = ConcreteDecoratorB(ConcreteDecoratorA(component))
print("Decorated Component AB:")
decorated_component_ab.operation()
if __name__ == "__main__":
main()
There is no need to compile anything in Python. The code is executed directly using the python command under Windows and the python3 command under MacOS / Linux.
@echo off
python main.py
#!/bin/bash
python3 main.py
Testing the Python Example
Now that all the files are ready and within their correct location they can be put to test. Python does not need to be compiled before being run. Therefore we can execute it right away to see what it will print in the Windows Command Prompt or in the MacOS / Linux Terminal.
Caveats
While the Decorator Pattern provides flexibility and allows for dynamic behavior addition, it is important to be mindful of these caveats to ensure its effective and efficient use.
-
Complexity
The Decorator Pattern, while powerful, can lead to an exponential increase in the number of classes and interfaces within the system as more decorators are added. This growth can introduce complexity and pose maintenance challenges. It is crucial to exercise restraint and avoid over-reliance on this pattern, particularly in simpler scenarios where less complex solutions might be more appropriate. -
Duplication of Functionality
The risk of functionality duplication across decorators is a potential pitfall, especially if the decorators are not meticulously designed. It is essential to circumvent redundancy and ensure that each decorator contributes a unique behavior or functionality to the component. -
Performance Overhead
The introduction of additional abstraction layers by the Decorator Pattern could potentially impact performance. Although the effect might be negligible in many instances, it’s vital to consider the performance implications, particularly in systems where performance is a critical factor. -
Serialization and Deserialization
The process of serializing and deserializing decorated objects can be intricate. If your application necessitates serialization, you might need to devise custom serialization logic to handle decorators appropriately.
More Information
So, where do I have my information from? In general, there are lots of places on the internet that offer great information. But that is just one source of information. Another source would be books. Those have the advantage that usually several people work on the same book before it is released, raising the chance for distribution of good and proven information.
Head First - Design Patterns
Building Extensible and Maintainable Object-Oriented Software
A book that has proven to be a valuable asset within my personal library is Head First - Design Patterns by the publisher O'Reilly. It has a fun approach to this topic, having recurring characters, such as the Guru, the Developer, the Skeptical Developer, Joe, and many more.
Java Design Patterns
A Hands-On Experience with Real-World Examples
Another highly valuable book is Java Design Patterns: A Hands-On Experience with Real-World Examples by the publisher Apress. It offers a conservative and rather scientific approach to the subject. It also offers code examples in Java, although any object-oriented programming language applies to the design patterns shown here.