装饰器模式通过统一接口和委托调用实现IO流的压缩与加密,关键在于顺序(先加密后压缩)、不修改原始类、正确处理资源与异常。

直接上结论:用装饰器模式给 IO 流加压缩和加密,关键不是“套类”,而是让每个装饰器实现同一接口、持有下游流引用、在 read()/write() 中做预/后处理并委托调用——顺序错了,解密就失败,压缩就乱码。
为什么不能直接改原始流类?
Java 的 InputStream 和 OutputStream 是抽象基类,但具体子类(如 FileInputStream)往往被 final 修饰或深度耦合底层资源;PHP 的 fopen() 返回资源句柄,无法继承扩展。硬改会破坏封装、影响复用,也违背开闭原则。
装饰器绕过这个问题:不碰原类,只写新类包装它。
Java 中用 FilterInputStream / FilterOutputStream 实现压缩+加密
Java 标准库已提供装饰器骨架,FilterInputStream 和 FilterOutputStream 就是抽象装饰器。你只需继承它们,重写 read() 或 write():
-
GZIPOutputStream和CipherOutputStream已是现成装饰器,可直接链式组合 - 但注意顺序:先加密再压缩 → 解压后再解密,否则解压时字节流被加密破坏,会抛
DataFormatException - 反向组合(先压缩后加密)才是安全路径,例如:
OutputStream cipher = new CipherOutputStream( new GZIPOutputStream(new FileOutputStream("out.enc")), cipherInstance ); - 读取时必须严格逆序:
CipherInputStream→GZIPInputStream→FileInputStream
PHP 中用自定义 stream wrapper 或装饰类模拟
PHP 没有原生 FilterStream 类,但有两种可行路径:
- 用
stream_wrapper_register()注册自定义流包装器(如compress://、encrypt://),但需处理全部流操作(stream_open、stream_read等),复杂度高,且无法动态叠加 - 更实用的是纯装饰类:定义统一接口
DataStream,所有组件(FileDataStream、GzipDataStream、AesDataStream)都实现它,并在read()/write()中调用$this->inner->read() - 示例关键逻辑:
class AesDataStream implements DataStream { public function __construct(private DataStream $inner, private string $key) {} public function read(): string { $raw = $this->inner->read(); return openssl_decrypt($raw, 'AES-128-CBC', $this->key, 0, substr($this->key, 0, 16)); } } - 注意:PHP 的
gzcompress()输出二进制,若后续要 base64 编码(比如存文本文件),得在装饰器里统一处理,否则下游拿到乱码
容易踩的坑:顺序、编码、资源生命周期
三个实际高频问题:
- 压缩和加密的顺序颠倒,导致解包失败 —— 记住:写入时「加密→压缩」,读取时「解压→解密」
- PHP 装饰器中没调用
fclose()或没透传close()方法,造成文件句柄泄漏 - Java 中
CipherOutputStream的doFinal()在close()时才触发,若忘记close(),最后一段加密数据就丢了
最易被忽略的是错误传播:装饰器内部出错(如密钥错误、gzip 损坏),必须原样 throw 异常,不能吞掉 —— 否则上游根本不知道哪一层挂了。

















