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