The decorator pattern enables you to decorate classes at runtime by using a technique akin to object composition. This is primarily useful where we want to instantiate objects with new responsibilities without making any code changes to the underlying classes. This article explores this pattern and shows you how to implement it by using the provided Java code examples.
Overview
The decorator pattern is one of the structural design patterns established by the “Gang of Four” (Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides). It leverages re-usability and flexibility in object-oriented design by solving recurring problems of adding a new state or behavior to objects at runtime. Although the idea rhymes with the object-oriented principle called inheritance, simple inheritance is not applicable under this circumstance because it is static and takes the entire class into account. This paves a new approach, imposing new responsibilities to an object dynamically as it focuses in providing a flexible alternative to extending subclass functionality.
Decorator Pattern
As the name suggests, the decorator pattern configures the functionality of specific object. This is a common pattern found in Java’s original IO classes used for reading and writing to sources outside JVM. For example, the InputStream and the OutputStream classes and their subclasses extensively use this pattern in their read and write operations. The implementation of these classes can be chained to efficiently read and write data from different sources, such as the native file system.
For example, note the abstract class OutputStream. It has an abstract method called read, that must be defined by the subclassed class. The method can be overridden to extend its behavior to alter or enhance its efficiency. The OutputStream implementation does the required job before delegating the responsibility to another OutputStream. The chain of implementation takes another OutputStream as a constructor argument. The concrete subclass, such as FileOutputStream or SocketOutputStream of the OutputStream, is the end of the pipeline, and finally writes the data without delegating the write responsibility to another stream. Therefore, it does not require another OutputStream object in their constructor argument.
Let us see how the use of the decorator pattern in the OutputStream class of the Java IO classes may lead to chaining several operations on an array of bytes before writing them to disk.
@Test public void testingDecoratorPattern() throws IOException { final File file = new File("myfile.dat"); final FileOutputStream fileOutputStream = new FileOutputStream(f); final BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream); final ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream); objectOutputStream.writeBoolean(true); objectOutputStream.writeInt(12); objectOutputStream.writeObject(new ArrayList<Integer>()); objectOutputStream.flush(); objectOutputStream.close(); bufferedOutputStream.close(); fileOutputStream.close(); assertTrue(file.exists()); }
The FileOutputStream object is used to write files to disk. The BufferedOutputStream object caches any calls to the write operation and writes several bytes at once. This leverages efficiency, especially when writing to disk. The built-in mechanism of object serialization in ObjectOutputStream writes objects and primitive types to a stream. The ObjectOutputStream, however, has no idea about where the files are being written. This is primarily used for delegating responsibility to another OutputStream.
Use of the Decorator Pattern
Suppose we want to create a class design with the provision that new features may be added. In such a case, the decorator pattern provides the client the ability to reinforce further implementation with whatever combination of features is desired, as shown in Figure 1.
Figure 1: The decorator pattern
Here, Tea is an abstract class subclassed by all the categories or variations of the product. The details instance variable is set in each of the subclasses that hold the specific information about its type. The getDetails() method returns the information about its type. The price() method is declared abstract by the parent class which the subclasses would define their own implementation. In addition to the type of teas available, we can implement as many as we want with numerous variation and combination. This is the crux of the decorator pattern.
Implementing a Decorator Pattern
Now, let’s see how we can implement the decorator pattern. The example is pretty simple and self-explanatory. The classes are laid out as follows.
package org.mano.example; public interface Car { void paint(); } package org.mano.example; public class ElectricCar implements Car { @Override public void paint() { // ... } } package org.mano.example; public class HybridCar implements Car { @Override public void paint() { // ... } } package org.mano.example; public abstract class CarDecorator implements Car { protected Car decoratedCar; public CarDecorator(Car car){ decoratedCar = car; } public void paint(){ decoratedCar.paint(); } } package org.mano.example; public class CarColorDecorator extends CarDecorator { public CarColorDecorator(Car car) { super (car); } @Override public void paint(){ decoratedCar.paint(); setTheme(decoratedCar); } private void setTheme(Car car){ // ... } } package org.mano.example; public class DPApp { public static void main(String[] args) { Car defaultHybridCar = new HybridCar(); Car redHybridCar = new CarColorDecorator(new HybridCar()); Car blueElectricCar = new CarColorDecorator(new ElectricCar()); defaultHybridCar.paint(); redHybridCar.paint(); blueElectricCar.paint(); } }
Conclusion
This is one of the most common patterns used in OOP. The power of this pattern lies in combining objects, such as an ObjectOutputStream, BufferedOutputStream, and FileOutputStream extend the abstract superclass OutputStream. Each subclass constructor takes an OutputStream object as a parameter. This would not have been possible without the decorator pattern. Otherwise, we had to create numerous classes to get the essence, which would have been a bad design.