The goal of this lab is to use the decorator design pattern, a structural design pattern, to solve a real-world problem.
Design patterns are proven solutions to solve recurring design problems to design flexible and reusable object-oriented software; that is, objects that are easier to implement, change, test and reuse.
Structural patterns deal with the composition of classes or objects while keeping them extensible, flexible and efficient.
The decorator design pattern is one of the twenty-three well-known Gang of Four (GoF) design patterns. It is classified under the category of structural patterns as it deals with structuring objects in a way that supports adding additional features dynamically.
The decorator pattern provides provides us with a new mechanism for adding new behavior to an object at run-time. It composes objects recursively to allow adding additional responsibilities to an object dynamically in a more flexible and efficient manner. It provides a better alternative compared to subclassing for extending functionality.
In this lab, we will work on the problem of creating a comprehensive logging library and attempt to answer the following key questions behind the Decorator pattern:
How to add new responsibilities to objects without making any code changes to the underlying classes?
How can we extend the class’ run-time behavior without making modifications to the underlying class?
Video
Objectives
In this lab you will
understand a real-world scenario and choose when to apply the appropriate design pattern.
design and implement the decorator design pattern.
write unit tests and apply Test-Driven Development (TDD).
Apache Maven is a build automation tool to build projects and manage their dependencies.
Getting Started
If your instructor is using GitHub classroom, you will need to accept the assignment using the link below, clone the template repository, and import it as a project into your IDE.
A developer is working on a comprehensive logging library, which allows writing log messages to multiple storage devices called Transports (e.g., Console, File, HTTP, Syslog, databases, etc.). Log message may be written in multiple formats (e.g., plaintext, JSON, CSV, YAML, HTML, Syslog, colorize, prettyPrint, etc.). These formats are common in logging libraries (e.g., log4j is a popular logging library that supports multiple logging formats).
The library is getting popular and users are always demanding adding new logging devices/transports (e.g., MySQL, Slack, FTP server, etc.) with custom formats:
Base logger with no formatter: BaseLogger
File logger with JSON formatter: FileJSONLogger
File logger with Colorized formatter: FileColorizedLogger
Console logger with YAML formatter: ConsoleYAMLLogger
HTTP logger with HTML formatter: HTTPHTMLLogger
HTTP logger with HTML and YAML formatter: HttpHtmlYamlLogger
File logger with JSON and YAML formatter: FileJsonYamlLogger
File logger with HTML and XML and JSON and YAML formatter: FileHtmlXmlJsonYamlLogger
The developer is rethinking his design as the current implementation is simply a maintenance nightmare! He needs to restructure his code in a way that allows adding additional behavior without making changes to the underlying classes.
He realized the need to design the library with the following goals in mind:
Add new responsibilities to objects without making any code changes to the underlying logging classes (Transport and Formatters classes).
The design should support the open-closed principle: “Classes should be open for extension but closed for modification”.
In his attempt to improve the design of his library, he went through two attempts:
First Attempt: One subclass per a combination of Transport and Formatter
He added formatters (JSON, CSV, plaintext, YAML, prettyprint, Colorize, etc.) to transports (File, HTTP, Database, Console, etc.) using inheritance as follows:
Whoa, this is a class explosion! Without even implementing this, we can see that this looks really bad and is a nightmare to maintain. He has realized how bad this solution is.
Why do we make heavy use of inheritance?
What happens when logging levels (“info”, “warning”, “error”) changes to include (“debug”, “fatal”) or additional settings are added (e.g., appending log messages with a text)?
How can we create different combinations of objects with different mix-and-match responsibilities, without ending up with N subclasses for each combination?
This approach is indeed problematic as minor changes will require changes to related subclasses. He is now considering another approach.
Second Attempt: Transports handle Formatters
In the second attempt, he decided to use instance variables and inheritance in the superclass to keep track of formatters. He created an abstract class with all the required instance variables and used methods that return boolean values (hasCSVFormat, hasJSONFormat ,etc.)
Whoa, this is much better!, Six classes in total! This looks like a great improvement and the best solution he can ever have!
However, do all concrete Loggers (File, Console, Database, HTTP, FTP) need these formatters?
Format may change. New formats are added, some are gone, layouts change and their internal details will also change.
Also, how do you get the number of combination loggers for a Transport with boolean variables?
New formatter will force us to add new methods and alter the superclass.
We may have Transports with no formatters needed. Yet, they will inherit methods like hasHTMLHeaderhasCSV, etc.
Enter the Decorator Pattern
He has seen that creating different combinations of objects for our logger and formatter objects with inheritance has not worked out very well. He got class explosions and complex designs. The second approach did not work out well as he added functionality to the base class that isn’t appropriate for some of the subclasses.
Given these failed two approaches in mind, he thought that this is indeed a good use case for the decorator pattern.
So, here’s what he should do instead: we’ll start with a logger and “decorate” it with the formatters at runtime. For example, if the databse logger wants to log messages in a database in two formats JSON and YAML, then we’ll:
publicabstractclassFormatDecoratorextends BaseLogger { BaseLogger logger;// All format decorators have to reimplement the getLabel method
publicabstract String getLabel();}
We pass BaseLogger to Formatter’s constructor and then pass Formatter to the Logger’s client.
The client thinks it’s talking to Logger but it’s actually talking to Formatter. Formatter’s methods can provide additional behavior to BaseLogger’s methods. Clients of the library should be able to combine formatters as listed below:
publicclassLoggerDemo{publicstaticvoidmain(String args[]){ BaseLogger logger =new FileLogger(); System.out.println(logger.getLabel()+". Level: "+ logger.getLevel());// create a console logger
BaseLogger logger2 =new ConsoleLogger();// decorate it with a CSV and HTML formatters
logger2 =new CSVFormatter(logger2); logger2 =new HTMLFormatter(logger2); System.out.println(logger2.getLabel()+". Level: "+ logger2.getLevel());// create a file logger
BaseLogger logger3 =new FileLogger();// decorate it with a JSON, CSV, and YAML formatters
logger3 =new JSONFormatter(logger3); logger3 =new CSVFormatter(logger3); logger3 =new YAMLFormatter(logger3); System.out.println(logger3.getLabel()+". Level: "+ logger3.getLevel());}}
Now, this will enable us to create different combinations of objects with different mix-and-match responsibilities, without ending up with N subclasses for each combination and without adding inappropriate responsibilities to concrete implementations.
Questions:
Complete the current implementation using the decorator design pattern as shown in the last UML diagram.
Explain how the decorator design pattern is a better alternative to the previous two attempts.
If your instructor is using GitHub classroom, then you should click on your class submission link,
link your GitHub username to your name if you have not already done so, accept the assignment, clone the
repository into your local
development environment, and push the code to the remote repository on GitHub. Please make sure that your
written
answers are included in either a README (Markdown) file or a PDF file.
Lab dues dates are listed on GitHub classroom unless otherwise
noted.
If your instructor is using GitHub classroom, your submission will be
auto-graded
by running the included unit tests as well as manually graded for correctness, style, and quality.
How to submit your lab to GitHub Classroom
The video below demonstrates how to submit your work to GitHub classroom
Extra Task [Optional]
If you are done with this activity, you may enable a continuos integration tool such as CircleCI ↗ to automatically run your JUnit test upon code changes. You may also add more unit tests to increase code coverage. Please embed the badge that shows the status of your build and test (passing/failing) as well as the coverage percentage into your README file (e.g.,
and ). Please be sure to fork the repository or push to a remote repository under your own account, so you can enable the integration of CI tools in your own account.