The Decorator Builder
The Decorator Pattern is useful, but the stacking of Decorators can make your code hard to read. Combine the Builder and Decorator patterns for more synergy.
Join the DZone community and get the full member experience.
Join For FreeIn my previous article, Is Inheritance Dead?, I discussed the advantages of using the decorator pattern over inheritance. While most readers agree that the decorator pattern (and composition in general) has many advantages over inheritance, composing decorators came up several times as one of the main pain-points of using the decorator pattern. In this short article, I will present a simple approach to overcome this difficulty.
Composing Decorators
Recall that the decorator pattern is designed in a way that multiple decorators can be stacked on top of each other, each adding a new functionality (see Is Inheritance Dead? for more details). Stacking decorators and choosing their order (referred to as composing decorators) can be difficult to implement and can lead to code that is difficult to read and maintain.
To illustrate, let’s consider the email service example discussed in Is Inheritance Dead? We can create an instance of IEmailService that is thread-safe, implements retries, has a cache and has verbose logging by composing decorators as follows:
IEmailService emailService = new EmailServiceCacheDecorator(
new EmailServiceLoggingDecorator(new EmailServiceRetryDecorator(
new EmailServiceLoggingDecorator(new EmailServiceThreadSafetyDecorator(
new EmailService())))));
As you can see, the resulting code can be a bit overwhelming. In addition, applying decorators in the wrong order can lead to incorrect behavior.
In contrast to a hierarchy issue, such as the exploding class hierarchy issue discussed in my previous post, the difficulty of composing decorators can be easily overcome by organizing the code a little bit differently.
The Decorator Builder
The decorator builder is not a new pattern, it's just a way of using the builder pattern to make composing decorators easier. This is particularly useful for public API's where simplicity is crucial.
The code below illustrates a decorator builder for the email service API discussed in the previous section:
public class EmailServiceBuilder
{
private IEmailService emailService = new EmailService();
public EmailServiceBuilder synchronize() {
emailService = new EmailServiceThreadSafetyDecorator(emailService);
return this;
}
public EmailServiceBuilder retry() {
emailService = new EmailServiceRetryDecorator(emailService);
return this;
}
public EmailServiceBuilder log() {
emailService = new EmailServiceLoggingDecorator(emailService);
return this;
}
public EmailServiceBuilder cache() {
emailService = new EmailServiceCacheDecorator(emailService);
return this;
}
public IEmailService build() {
IEmailService es = emailService;
// Reset the builder so it can be reused
emailService = new EmailService();
return es;
}
}
Using this decorator builder, the same instance of IEmailService discussed above can be created as follows:
IEmailService emailService = new EmailServiceBuilder().synchronize()
.log().retry().log().cache().build();
Notice that the resulting code is easier to read, is verbose and less error prone than the original code. Furthermore, the order in which the decorators are added to the builder is the same as the order in which the decorators will be executed at runtime. In fact, without the builder, the order was inversed, which can cause confusion.
Conclusion
Composing decorators is not really a drawback of the decorator pattern. In fact, like any other Java code, it can be dramatically simplified by organizing the code a little bit differently (e.g. by using more functions and classes). In this article, we used the builder pattern to simplify composing decorators. As shown, the resulting code is simple and easy to maintain.
Opinions expressed by DZone contributors are their own.
Comments