跳转到主内容
新派编程网 - 新潮编程理念,全新技术课堂!

怎么利用 装饰器模式 在不修改原有 IO 流代码的基础上增加动态压缩与加密功能

装饰器模式通过统一接口和委托调用实现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 异常,不能吞掉 —— 否则上游根本不知道哪一层挂了。

相关文章