library
intermediateDecorator Pattern & I/O Streams
Understand how Java I/O streams use the Decorator pattern and how to apply it in your own designs.
A decorator implements the same interface as the thing it wraps, and adds behaviour around it.
Decorator = gift wrapping. The gift (core object) stays the same. Each wrapping layer (decorator) adds something: tissue paper (buffering), a bow (compression), a card (logging). You can combine any layers in any order.
Key Concepts
1
InputStream in = new BufferedInputStream(
new GZIPInputStream(
new FileInputStream("data.gz")));2
Read that from the inside out.
FileInputStream reads raw bytes from the file
GZIPInputStream decompresses them
BufferedInputStream reads them in 8 KB chunks3
Each layer is an InputStream, so each can wrap any other, and the caller sees one InputStream.
InputStream
4
What it replaces: a class per combination.
BufferedFileInputStream
GzipFileInputStream
BufferedGzipFileInputStream
BufferedEncryptedGzipFileInputStream ...5
Three features need seven classes. Four need fifteen. Decoration needs four.
6
The structure.
component InputStream the interface
concrete component FileInputStream the real thing
decorator FilterInputStream holds one, delegates
concrete decorators Buffered..., GZIP..., Cipher...7
Writing your own is this shape:
class LoggingRepo implements Repo {
private final Repo inner;
LoggingRepo(Repo inner) { this.inner = inner; }8
public Order find(String id) {
log.info("find {}", id);
return inner.find(id); // delegate
}
}9
Repo repo = new LoggingRepo(new CachingRepo(new JdbcRepo(ds)));10
Where it earns its place. Logging, caching, retrying, rate limiting, metrics and validation — each a
concern you want to add without touching the class that does the work, and in whatever combination.
11
The complaint about Java I/O is fair. Three nested constructors to read a compressed file is verbose, and
the order matters in ways that are not obvious — buffering outside compression is right, inside is nearly
useless.
12
Which is why the convenience methods exist. Files.readString and Files.newBufferedReader assemble the
usual stack for you. Build it by hand when the combination is unusual.
Files.readStringFiles.newBufferedReader
13
What the interviewer is probing.1. "Where do you see the Decorator pattern in the JDK?" Probing: recognition in a familiar
API. Stalls: "Nowhere obvious." Moves up: java.io — new BufferedInputStream(new
GZIPInputStream(new FileInputStream(f))), each layer an InputStream adding one behaviour.
14
2. "Why does order matter when nesting streams?" Probing: whether you understand what each
layer does. Stalls: "It does not." Moves up: buffering outside decompression batches the
compressed reads usefully; inside it buffers already-decompressed bytes and achieves little.
15
3. "What does Decorator avoid?" Probing: the combinatorial problem. Stalls: "Extra classes."
Moves up: a class per combination — three features would need seven subclasses, four would need
fifteen.
16
4. "How would you apply it outside I/O?" Probing: transfer to your own code. Stalls: "It is
an I/O thing." Moves up: wrapping a repository or client to add logging, caching, retries or
metrics, in any combination, without touching the class doing the work.