Use Cases and Examples of GoF Decorator Pattern for IO

In .NET, there are a bunch of stream decorators, like BufferedStream, CryptoStream, GzipStream, etc. All those decorate Stream class.


Let's understand components of Decorator pattern before going through java IO classes.

enter image description here

Decorator pattern has four components

  1. Component: The Component defines the interface for objects that can have responsibilties added dynamically
  2. ConcreteComponent: It is simply an implementation of Component interface
  3. Decorator: The Decorator has a reference to a Component, and also conforms to the Component interface. Decorator is essentially wrapping the Component
  4. ConcreteDecorator: The ConcreteDecorator just adds responsibilities to the original Component.

The decorator pattern can be used to extend (decorate) the functionality of a certain object statically, or in some cases at run-time, independently of other instances of the same class, provided some groundwork is done at design time. This is achieved by designing a new Decorator class that wraps the original class.

Now let's map these concepts to java.io pacakge classes.

Component:

InputStream :

This abstract class is the superclass of all classes representing an input stream of bytes.

Applications that need to define a subclass of InputStream must always provide a method that returns the next byte of input.

public abstract int read() is an abstract method.

ConcreteComponent:

FileInputStream:

A FileInputStream obtains input bytes from a file in a file system. What files are available depends on the host environment.

FileInputStream is meant for reading streams of raw bytes such as image data. For reading streams of characters, consider using FileReader.

Examples of all ConcreteComponents of InputStream:

AudioInputStream, ByteArrayInputStream, FileInputStream, FilterInputStream, 
InputStream, ObjectInputStream, PipedInputStream, SequenceInputStream, 
StringBufferInputStream

Decorator:

FilterInputStream:

A FilterInputStream contains some other input stream, which it uses as its basic source of data, possibly transforming the data along the way or providing additional functionality.

Please note that FilterInputStream implements InputStream => Decorator implements Component as shown in UML diagram.

public class FilterInputStream
extends InputStream

ConcreteDecorator:

BufferedInputStream

A BufferedInputStream adds functionality to another input stream-namely, the ability to buffer the input and to support the mark and reset methods.

Examples of all ConcreteDecorators:

BufferedInputStream, CheckedInputStream, CipherInputStream, DataInputStream, 
DeflaterInputStream, DigestInputStream, InflaterInputStream, 
LineNumberInputStream, ProgressMonitorInputStream, PushbackInputStream

Working example code:

I have used BufferedInputStream to read each character of a word, which has been stored in a text file a.txt

BufferedInputStream bis = new BufferedInputStream(new FileInputStream(new File("a.txt")));
while(bis.available()>0)
{
        char c = (char)bis.read();
        System.out.println("Char: "+c);;
}

When to use this pattern:

  1. Object responsibilities and behaviours should be dynamically added/removed
  2. Concrete implementations should be decoupled from responsibilities and behaviours
  3. When sub - classing is too costly to dynamically add/remove responsibilities

InputStream is an abstract class. Most concrete implementations like BufferedInputStream, GzipInputStream, ObjectInputStream, etc. have a constructor that takes an instance of the same abstract class. That's the recognition key of the decorator pattern (this also applies to constructors taking an instance of the same interface).

When such a constructor is used, all methods will delegate to the wrapped instance, with changes in the way the methods behave. For example, buffering the stream in memory beforehand, decompressing the stream beforehand or interpreting the stream differently. Some even have additional methods that finally also delegate further to the wrapped instance. Those methods decorate the wrapped instance with extra behaviour.

Let's say that we have a bunch of serialized Java objects in a Gzipped file and that we want to read them quickly.

First open an inputstream of it:

FileInputStream fis = new FileInputStream("/objects.gz");

We want speed, so let's buffer it in memory:

BufferedInputStream bis = new BufferedInputStream(fis);

The file is gzipped, so we need to ungzip it:

GzipInputStream gis = new GzipInputStream(bis);

We need to unserialize those Java objects:

ObjectInputStream ois = new ObjectInputStream(gis);

Now we can finally use it:

SomeObject someObject = (SomeObject) ois.readObject();
// ...

The benefit is that you have a lot of freedom to decorate the stream using one or more various decorators to suit your needs. That's much better than having a single class for every possible combination like ObjectGzipBufferedFileInputStream, ObjectBufferedFileInputStream, GzipBufferedFileInputStream, ObjectGzipFileInputStream, ObjectFileInputStream, GzipFileInputStream, BufferedFileInputStream, etc.

Note that when you're about to close the stream, just closing the outermost decorator is sufficient. It will delegate the close call all the way to the bottom.

ois.close();

See also:

  • Examples of GoF Design Patterns in Java's core libraries