前言:为什么Java IO流是装饰器模式的经典案例?
摘要:本文深入解析装饰器模式在Java IO流中的经典应用。首先通过IO流组合使用的困惑引出装饰器模式解决"类爆炸"问题的必要性,详细讲解装饰器模式的核心定义、四大角色(原始类、抽象组件、装饰抽象类、具体装饰者)及其在IO流中的对应实现。通过手写简易IO流模拟代码和JDK源码逐层分析(FilterInputStream、BufferedInputStream、DataInputStream),深入理解装饰器的实现机制。对比装饰器与继承方案的优劣,展示装饰器如何通过"组合+委托"避免类爆炸。最后提供Web请求处理链和数据流加密压缩管道两个实战案例,并总结装饰器模式的优缺点及适用场景,帮助Java开发者深入掌握这一重要设计模式。
当你第一次接触Java IO包时,可能会被FileInputStream、BufferedInputStream、DataInputStream这些类的组合用法搞得晕头转向。为什么不能一个类搞定所有功能?为什么需要层层包装?这背后正是装饰器模式在优雅地解决"功能组合爆炸"的问题。
试想一下,如果Java IO采用继承来实现所有功能组合:
FileInputStream(基础文件读取)BufferedFileInputStream(带缓冲的文件读取)DataFileInputStream(带数据类型的文件读取)BufferedDataFileInputStream(带缓冲和数据类型...)
你会发现,每增加一个功能,就需要创建一个新的子类。如果有N个功能,理论上需要2N2^N2N个子类!这就是类爆炸问题 。
而装饰器模式通过"组合优于继承"的思想,让每个功能成为一个独立的装饰器,可以动态地、透明地添加到对象上。这正是Java IO流设计的精妙之处。
一、装饰器模式核心定义与解决的痛点
1.1 官方定义
装饰器模式(Decorator Pattern)是一种结构型设计模式,允许向一个现有的对象添加新的功能,同时又不改变其结构。它通过创建一个包装对象(装饰器)来包裹原始对象,并在保持原始对象接口一致的前提下,提供额外的功能。
1.2 解决的三大痛点
- 开闭原则:在不修改现有代码的情况下扩展功能
- 组合爆炸:避免通过继承产生大量子类
- 动态扩展:运行时动态添加或移除功能
1.3 核心思想:包装与委托
原始对象 → 被装饰器包装 → 装饰器委托给原始对象 → 添加额外功能
装饰器持有原始对象的引用,所有调用都先委托给原始对象,再执行自己的增强逻辑。
二、装饰器模式四大角色详解
2.1 原始类(ConcreteComponent)
角色 :被装饰的原始对象,提供基础功能。
IO流对应 :FileInputStream、FileOutputStream
2.2 抽象组件(Component)
角色 :定义原始对象和装饰器的共同接口。
IO流对应 :InputStream、OutputStream(抽象类)
2.3 装饰抽象类(Decorator)
角色 :持有一个Component引用,实现Component接口。
IO流对应 :FilterInputStream、FilterOutputStream
2.4 具体装饰者(ConcreteDecorator)
角色 :实现具体的增强功能。
IO流对应 :BufferedInputStream、DataInputStream、PushbackInputStream
三、手写简易IO流模拟装饰器代码
让我们通过一个简化的例子,理解装饰器模式如何工作:
java
// 1. 抽象组件:输入流接口
interface InputStream {
int read();
void close();
}
// 2. 原始类:文件输入流(基础功能)
class FileInputStream implements InputStream {
private String filename;
public FileInputStream(String filename) {
this.filename = filename;
System.out.println("打开文件: " + filename);
}
@Override
public int read() {
System.out.println("从文件读取1个字节");
return 1; // 模拟读取
}
@Override
public void close() {
System.out.println("关闭文件: " + filename);
}
}
// 3. 装饰抽象类:过滤器输入流
abstract class FilterInputStream implements InputStream {
protected InputStream in; // 持有被装饰对象的引用
protected FilterInputStream(InputStream in) {
this.in = in;
}
// 默认实现:直接委托给被装饰对象
@Override
public int read() {
return in.read();
}
@Override
public void close() {
in.close();
}
}
// 4. 具体装饰者:缓冲输入流
class BufferedInputStream extends FilterInputStream {
private byte[] buffer = new byte[8192];
private int position = 0;
private int count = 0;
public BufferedInputStream(InputStream in) {
super(in);
System.out.println("添加缓冲功能");
}
@Override
public int read() {
// 缓冲增强逻辑
if (position >= count) {
System.out.println("缓冲已空,从底层流填充缓冲区");
count = 8192; // 模拟填充
position = 0;
}
System.out.println("从缓冲区读取1个字节");
position++;
return 1;
}
}
// 5. 具体装饰者:数据输入流(支持读取Java基本类型)
class DataInputStream extends FilterInputStream {
public DataInputStream(InputStream in) {
super(in);
System.out.println("添加数据类型读取功能");
}
public int readInt() {
// 读取4个字节组合成int
System.out.println("读取int类型数据");
return 1024;
}
public String readUTF() {
System.out.println("读取UTF字符串");
return "Hello";
}
}
// 6. 客户端测试
public class DecoratorDemo {
public static void main(String[] args) {
System.out.println("=== 基础文件流 ===");
InputStream fileStream = new FileInputStream("test.txt");
fileStream.read();
fileStream.close();
System.out.println("\n=== 添加缓冲装饰 ===");
InputStream bufferedStream = new BufferedInputStream(
new FileInputStream("test.txt")
);
bufferedStream.read();
bufferedStream.close();
System.out.println("\n=== 多层装饰:缓冲 + 数据类型 ===");
DataInputStream dataStream = new DataInputStream(
new BufferedInputStream(
new FileInputStream("data.bin")
)
);
dataStream.read(); // 继承的read方法
dataStream.readInt(); // 增强的方法
dataStream.readUTF(); // 增强的方法
dataStream.close();
}
}
代码解析:
- 接口统一 :所有装饰器和原始类都实现
InputStream接口 - 组合关系 :装饰器持有被装饰对象的引用(
protected InputStream in) - 透明装饰 :
BufferedInputStream可以当作普通InputStream使用 - 功能叠加 :可以任意组合装饰器,如
DataInputStream(BufferedInputStream(FileInputStream))
运行结果:
=== 基础文件流 ===
打开文件: test.txt
从文件读取1个字节
关闭文件: test.txt
=== 添加缓冲装饰 ===
打开文件: test.txt
添加缓冲功能
缓冲已空,从底层流填充缓冲区
从缓冲区读取1个字节
关闭文件: test.txt
=== 多层装饰:缓冲 + 数据类型 ===
打开文件: data.bin
添加缓冲功能
添加数据类型读取功能
缓冲已空,从底层流填充缓冲区
从缓冲区读取1个字节
读取int类型数据
读取UTF字符串
关闭文件: data.bin
四、JDK IO流源码逐层分析
4.1 源码结构总览
java.io.InputStream (抽象组件)
├── FileInputStream (原始类)
├── ByteArrayInputStream (原始类)
└── FilterInputStream (装饰抽象类)
├── BufferedInputStream (具体装饰者)
├── DataInputStream (具体装饰者)
└── PushbackInputStream (具体装饰者)
4.2 FilterInputStream:装饰器的骨架
java
public class FilterInputStream extends InputStream {
protected volatile InputStream in; // 关键:持有被装饰对象
protected FilterInputStream(InputStream in) {
this.in = in;
}
// 所有方法都委托给in
public int read() throws IOException {
return in.read();
}
public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
}
public int read(byte b[], int off, int len) throws IOException {
return in.read(b, off, len);
}
// ... 其他方法也是类似委托
}
设计要点:
protected修饰的in:子类可以直接访问- 默认实现都是简单委托:子类可以选择性重写
- 构造器是
protected:防止直接实例化
4.3 BufferedInputStream:缓冲装饰器
java
public class BufferedInputStream extends FilterInputStream {
private static int DEFAULT_BUFFER_SIZE = 8192;
protected volatile byte buf[]; // 缓冲区
public BufferedInputStream(InputStream in) {
this(in, DEFAULT_BUFFER_SIZE);
}
public BufferedInputStream(InputStream in, int size) {
super(in); // 调用父类构造器,设置in
buf = new byte[size];
}
// 重写read方法,添加缓冲逻辑
public int read() throws IOException {
if (pos >= count) {
fill(); // 缓冲为空时填充
if (pos >= count)
return -1;
}
return getBufIfOpen()[pos++] & 0xff;
}
// 关键:填充缓冲区时调用被装饰对象的read
private void fill() throws IOException {
// ... 省略细节
count = in.read(buffer, 0, buffer.length); // 委托给底层流
pos = 0;
}
}
4.4 DataInputStream:数据类型装饰器
java
public class DataInputStream extends FilterInputStream
implements DataInput { // 额外接口
public DataInputStream(InputStream in) {
super(in);
}
// 增强的方法:读取基本类型
public final int readInt() throws IOException {
int ch1 = in.read(); // 委托
int ch2 = in.read();
int ch3 = in.read();
int ch4 = in.read();
return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + ch4);
}
// 仍然提供基础的read方法(继承自FilterInputStream)
}
4.5 客户端使用示例
java
// 经典的三层装饰
InputStream input = new DataInputStream(
new BufferedInputStream(
new FileInputStream("data.dat")
)
);
// 等价于:
FileInputStream fileIn = new FileInputStream("data.dat");
BufferedInputStream bufferedIn = new BufferedInputStream(fileIn);
DataInputStream dataIn = new DataInputStream(bufferedIn);
五、装饰器 vs 继承:为什么选择装饰器?
5.1 继承方案的致命缺陷
假设用继承实现IO流功能:
java
// 方案一:多层继承(类爆炸)
class FileInputStream { /* 基础功能 */ }
class BufferedFileInputStream extends FileInputStream { /* +缓冲 */ }
class DataFileInputStream extends FileInputStream { /* +数据类型 */ }
class BufferedDataFileInputStream extends ? { /* 该继承谁? */ }
// 方案二:接口爆炸
interface Buffered { void setBufferSize(); }
interface DataReader { int readInt(); }
interface Pushback { void unread(); }
class SuperInputStream extends FileInputStream
implements Buffered, DataReader, Pushback {
// 需要实现所有功能,违反单一职责
}
问题:
- 类数量指数增长 :3个功能需要7个类(23−12^3-123−1)
- 功能组合困难 :
BufferedDataFileInputStream应该继承谁? - 代码重复:缓冲逻辑需要在多个子类中重复实现
5.2 装饰器方案的优势
装饰器模式通过"组合+委托"的方式,完美解决了继承方案的问题:
java
// 每个功能独立,自由组合
new DataInputStream( // 功能C
new BufferedInputStream( // 功能B
new FileInputStream("file.txt") // 功能A
)
);
// 也可以只要A+B
new BufferedInputStream(
new FileInputStream("file.txt")
);
// 或者只要A+C
new DataInputStream(
new FileInputStream("file.txt")
);
优势对比表:
| 维度 | 继承方案 | 装饰器方案 |
|---|---|---|
| 类数量 | 2N2^N2N-1(指数级) | N+2(线性级) |
| 功能组合 | 编译时确定 | 运行时动态组合 |
| 扩展性 | 需要修改父类 | 新增装饰器即可 |
| 单一职责 | 违反(多功能混合) | 遵守(一个装饰器一个功能) |
5.3 核心结论
装饰器模式通过"组合+委托"替代"继承",将功能正交分解为独立的装饰器,实现了功能的动态、透明、可插拔的扩展,完美解决了多层继承导致的类爆炸问题。
六、实战业务场景案例
6.1 场景一:Web请求处理链
java
// 抽象组件:请求处理器
interface RequestHandler {
void handle(HttpRequest request, HttpResponse response);
}
// 具体组件:业务处理器
class BusinessHandler implements RequestHandler {
public void handle(HttpRequest req, HttpResponse resp) {
System.out.println("处理业务逻辑");
}
}
// 装饰器抽象类
abstract class HandlerDecorator implements RequestHandler {
protected RequestHandler wrapped;
public HandlerDecorator(RequestHandler handler) {
this.wrapped = handler;
}
@Override
public void handle(HttpRequest req, HttpResponse resp) {
wrapped.handle(req, resp);
}
}
// 具体装饰器:日志记录
class LoggingDecorator extends HandlerDecorator {
public LoggingDecorator(RequestHandler handler) {
super(handler);
}
@Override
public void handle(HttpRequest req, HttpResponse resp) {
System.out.println("[" + new Date() + "] 开始处理请求: " + req.getPath());
long start = System.currentTimeMillis();
super.handle(req, resp); // 委托
long cost = System.currentTimeMillis() - start;
System.out.println("请求处理完成,耗时: " + cost + "ms");
}
}
// 具体装饰器:权限校验
class AuthDecorator extends HandlerDecorator {
public AuthDecorator(RequestHandler handler) {
super(handler);
}
@Override
public void handle(HttpRequest req, HttpResponse resp) {
if (!checkPermission(req)) {
resp.setStatus(403);
return;
}
super.handle(req, resp);
}
private boolean checkPermission(HttpRequest req) {
// 权限校验逻辑
return true;
}
}
// 使用:动态组合功能
public class WebServer {
public static void main(String[] args) {
// 基础业务处理器
RequestHandler handler = new BusinessHandler();
// 添加日志和权限校验
handler = new LoggingDecorator(
new AuthDecorator(handler)
);
// 处理请求(自动执行:权限校验→日志记录→业务处理)
handler.handle(new HttpRequest(), new HttpResponse());
}
}
6.2 场景二:数据流加密压缩管道
java
// 模拟数据处理器
interface DataProcessor {
byte[] process(byte[] data);
}
// 基础处理器:数据验证
class ValidationProcessor implements DataProcessor {
public byte[] process(byte[] data) {
System.out.println("数据验证通过,长度: " + data.length);
return data;
}
}
// 加密装饰器
class EncryptionDecorator implements DataProcessor {
private DataProcessor processor;
public EncryptionDecorator(DataProcessor processor) {
this.processor = processor;
}
public byte[] process(byte[] data) {
System.out.println("加密数据");
byte[] encrypted = encrypt(data);
return processor.process(encrypted); // 委托给下一个处理器
}
private byte[] encrypt(byte[] data) {
// 模拟加密
return data;
}
}
// 压缩装饰器
class CompressionDecorator implements DataProcessor {
private DataProcessor processor;
public CompressionDecorator(DataProcessor processor) {
this.processor = processor;
}
public byte[] process(byte[] data) {
System.out.println("压缩数据,压缩前: " + data.length + " bytes");
byte[] compressed = compress(data);
byte[] result = processor.process(compressed);
System.out.println("压缩后: " + compressed.length + " bytes");
return result;
}
private byte[] compress(byte[] data) {
// 模拟压缩
return data;
}
}
// 客户端:构建处理管道
public class DataPipeline {
public static void main(String[] args) {
// 构建处理链:验证 → 加密 → 压缩
DataProcessor pipeline = new CompressionDecorator(
new EncryptionDecorator(
new ValidationProcessor()
)
);
byte[] data = "Hello, Decorator Pattern!".getBytes();
pipeline.process(data);
}
}
七、装饰器模式优缺点总结
7.1 优点
- 符合开闭原则:新增功能无需修改现有代码
- 避免类爆炸 :N个功能只需要N个装饰器类,而不是2N个子类
- 动态组合:运行时可以任意组合功能
- 单一职责:每个装饰器只负责一个功能增强
- 透明性:装饰后的对象可以当作原始对象使用
7.2 缺点
- 多层装饰复杂度:过度使用会导致代码难以理解
- 初始化复杂:创建对象时需要多层new
- 类型识别困难:装饰后的对象类型信息可能丢失
- 设计难度:需要设计良好的抽象接口
7.3 适用场景
- 需要动态、透明地给对象添加功能
- 不适合用继承扩展(会导致类爆炸)
- 功能可以正交分解(一个功能一个装饰器)
- 需要运行时组合功能