Decorator pattern: a pattern for dynamic class expansion
If you’re looking to extend the functionalities of an existing class in object-oriented software, you have two choices. The easiest option is to implement sub-classes that complement the base class. But this can be confusing. As an alternative, you can use a decorator entity according to the so-called decorator design pattern. The pattern, which is one of the 23 GoF design patterns, allows for a dynamic expansion of classes while the software is running. It works without endlessly long, difficult-to-understand inheritance hierarchies.
Find out what exactly a decorator pattern is and what its advantages and disadvantages are. In the following, we’ll also provide a graphic and an example to explain how this works.
What is the decorator pattern?
The decorator design pattern (decorator pattern in short) is a design strategy revealed in 1994 to expand the functionality of classes in object-oriented computer software. According to the pattern, any object can be expanded by a desired behavior without influencing the functionalities of other objects of the same classes. Structurally, the decorator pattern resembles the “chain of responsibility” pattern. But in contrast to the “chain of responsibility” pattern, inquiries are being received by all classes via a central processor.
The software component that is to be expanded is “decorated” according to the decorator design pattern. One or more decorator classes completely enclose the component. Each decorator is of the same type as the enclosed component and, therefore, has the same interface. In this way, incoming method calls can be easily delegated to the linked component while a functionality is being carried out. Calls can also be directly processed within the decorator.
What’s the purpose of a decorator design pattern?
Much like other GoF patterns, for example the strategy pattern or the builder pattern, the decorator pattern aims to make components of object-oriented software more flexible and easier to reuse. To this end, the approach lets developers add and remove dependencies of an object dynamically, and where necessary, during runtime. This makes the pattern a good alternative to using sub-classes. Sub-classes can supplement a class, but do not allow for adjustments to be made during runtime.
A software component can be expanded with any number of decorator classes. These extensions are invisible, which means one does not notice if an actual class is preceded by additional classes.
Decorator pattern: UML diagram for visualization
The decorator or decorator class (ConcreteDecorator) has the same interface as the software component to be decorated (ConcreteComponent) and is of the same type. This must be given to handle calls which are forwarded either unchanged or changed if the decorator doesn’t process them. Conceptually, the decorator pattern defines an elementary interface – which is basically an abstract super class – as a “component”.
The interaction between the basic component and the decorator is best illustrated the form of a UML class diagram. Therefore, we’ve used the modeling language for object-oriented programming in a graphic, illustrating the decorator design pattern below.
The advantages and disadvantages of the decorator patterns
Considering the decorator design pattern when designing software pays off for several reasons. First and foremost, there is the high degree of flexibility when using the decorator structure: the functionalities of classes can be expanded during the compilation and during runtime without the need for confusing inheritance-based class hierarchies. This significantly improves the readability of the program code.
Because functionality is split across multiple decorator classes, the performance of the software can be increased. This makes it easy to retrieve and initiate specific functions. With a complex base class that permanently provides all functions, this resource-optimized option is not available.
However, development using the decorator pattern does have some disadvantages. Using the pattern increases the complexity of the software. The decorator interface, in particular, usually contains a lot of code and is often linked to many terms, rendering it far from beginner friendly. Another disadvantage is the large number of decorator objects, for which a separate systematization is recommended to avoid similar overview problems when working with subclasses. Long call chains of the decorated objects (i.e. the extended software components) make it harder to spot errors and debug in general.
Advantages | Disadvantages |
---|---|
High degree of flexibility | High complexity of software (especially decorator interface) |
Expansion of function of classes without inheritance | Not beginner-friendly |
Readable program code | High number of objects |
Resource-optimized functionalities | Difficult debugging process |
Decorator design pattern: typical use examples
The decorator pattern provides the basis for dynamic and transparent expandable objects of software. Components of graphic user interfaces (GUIs) are common areas of application of the pattern. For example, if a text field is to be framed, a decorator can be added “invisibly” between the text field object and the call to insert the new interface element.
Well-known examples for the implementation of the decorator design pattern are the so-called stream classes of the Java library, which are responsible for handling the input and output of data. Here, decorator classes are used to add new properties and status information to the data stream or to provide new interfaces.
But Java is not the only programming language making widespread use of decorator patterns. The following programming languages also rely on the design pattern:
- C++
- C#
- Go
- JavaScript
- Python
- PHP
Practical examples for the implementation of decorator patterns
The overview of advantages and disadvantages of the decorator design pattern shows that it’s not suitable for all types of software. But where a class has to be modified subsequently and especially in projects where this cannot be facilitated by using sub-classes, the design pattern is a great solution.
In this case, the starting point is software that makes the names of people accessible via the abstract class “Employees”. However, the first letter of the retrieved names is always lowercase. Since a subsequent adjustment is impossible, the decorator class “EmployeeDecorator” is implemented, which operates via the same interface and also enables the getName() method to be called. In addition, the decorator receives a logic to ensure the first letter is correctly capitalized. The appropriate code example looks like this:
public class EmployeeDecorator implements Person {
private Employee employee;
public Employee Decorator(Employee employee){
this.employee = employee;
}
public String getName(){
// call the method of employee class
String name = employee.getName();
// Ensure first letter is capitalized
name = Character.toUpperCase(name.charAt(0))
+ name.substring(1, name.length());
return name;
}
}