Fundamentals 15 min read

Why Inheritance Fails: Mastering the Decorator Pattern in Java

This article explains the Decorator design pattern, contrasting it with inheritance, and shows how to dynamically extend Java classes—especially I/O streams—using composition, complete with code examples, class diagrams, advantages, drawbacks, and practical usage scenarios.

JavaEdge
JavaEdge
JavaEdge
Why Inheritance Fails: Mastering the Decorator Pattern in Java

1. Introduction

The article starts by stating that there are two ways to add behavior to a class or object: inheritance (static) and association (dynamic via a decorator).

2. Definition

Decorator is a structural pattern that dynamically adds responsibilities to an object without altering its interface, keeping the added functionality transparent to the client.

3. Architecture

Component : abstract interface for objects that can be decorated.

ConcreteComponent : concrete class implementing the component.

Decorator : abstract class extending the component and holding a reference to a component.

ConcreteDecorator : concrete decorator adding new responsibilities.

Decorator class diagram
Decorator class diagram

4. Java I/O Example

Java I/O provides many classes (FileInputStream, BufferedInputStream, DataInputStream, etc.). To read a file with buffering, the usual code is:

InputStream in = new FileInputStream("/user/javaedge/test.txt");
InputStream bin = new BufferedInputStream(in);
byte[] data = new byte[128];
while (bin.read(data) != -1) {
    // ...
}

This approach requires creating a raw stream and then wrapping it, which feels cumbersome. A hypothetical BufferedFileInputStream would simplify the code:

InputStream bin = new BufferedFileInputStream("/user/javaedge/test.txt");
byte[] data = new byte[128];
while (bin.read(data) != -1) {
    // ...
}

However, extending every InputStream subclass with a buffered version would explode the class hierarchy.

4.1 Inheritance Design Issues

Creating a buffered subclass for each concrete stream (e.g., BufferedFileInputStream, BufferedPipedInputStream) leads to a combinatorial explosion when additional features like DataInputStream are needed.

4.2 Decorator Design Solution

By using composition, a BufferedInputStream can wrap any InputStream and add buffering without altering the original class. The same applies to DataInputStream for primitive‑type reads.

public abstract class InputStream {
    public int read(byte[] b) throws IOException { return read(b, 0, b.length); }
    public abstract int read(byte[] b, int off, int len) throws IOException;
    // other methods omitted for brevity
}

public class BufferedInputStream extends InputStream {
    protected volatile InputStream in;
    protected BufferedInputStream(InputStream in) { this.in = in; }
    @Override public int read(byte[] b, int off, int len) throws IOException { return in.read(b, off, len); }
    // additional buffering logic here
}

public class DataInputStream extends InputStream {
    protected volatile InputStream in;
    protected DataInputStream(InputStream in) { this.in = in; }
    public int readInt() throws IOException { /* read 4 bytes and convert */ }
    // other primitive read methods
}

Both decorators inherit from InputStream but delegate actual work to the wrapped stream, avoiding code duplication.

4.3 FilterInputStream – Reducing Duplication

Java introduces FilterInputStream as a common abstract decorator. All concrete decorators (e.g., BufferedInputStream, DataInputStream) extend it, inheriting default delegations for methods like read(), skip(), close(), etc.

public class FilterInputStream extends InputStream {
    protected volatile InputStream in;
    protected FilterInputStream(InputStream in) { this.in = in; }
    @Override public int read() throws IOException { return in.read(); }
    @Override public int read(byte[] b) throws IOException { return in.read(b); }
    @Override public long skip(long n) throws IOException { return in.skip(n); }
    @Override public void close() throws IOException { in.close(); }
    // other delegations omitted
}

5. Comparison with Proxy Pattern

Both Proxy and Decorator add behavior, but Proxy usually adds unrelated responsibilities (e.g., remote access, security) and hides the underlying object, whereas Decorator adds responsibilities that are conceptually part of the original object and keeps the interface identical.

6. Advantages

Dynamic, transparent addition of responsibilities.

Better low‑coupling compared to inheritance.

Supports multiple independent extensions by stacking decorators.

Adheres to the Open/Closed Principle.

7. Disadvantages

Creates many small objects, increasing system complexity.

Debugging can be harder because behavior is spread across several decorators.

8. When to Use

Use Decorator when you need to add responsibilities to individual objects at runtime without affecting other objects, especially when inheritance would lead to an explosion of subclasses or when the class is final.

9. Variants

Transparent Decorator : client works only with the abstract component type.

Half‑Transparent Decorator : client can reference concrete decorators to use added methods.

10. Summary

The Decorator pattern solves the problem of rigid inheritance by replacing it with composition, allowing flexible, runtime‑configurable extensions while keeping the original interface unchanged.

Reference: https://zh.wikipedia.org/wiki/装饰模式
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Design PatternsJavaSoftware ArchitectureDecorator PatternInheritancecomposition
JavaEdge
Written by

JavaEdge

First‑line development experience at multiple leading tech firms; now a software architect at a Shanghai state‑owned enterprise and founder of Programming Yanxuan. Nearly 300k followers online; expertise in distributed system design, AIGC application development, and quantitative finance investing.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.