当前位置: 首页 > news >正文

设计模式之装饰模式

装饰模式(Decorator Pattern)是结构型设计模式里很有代表性的一员,它的核心思想是:在不改变原有对象结构的情况下,动态地给对象添加新的功能

有点像在奶茶里加珍珠加布丁,不需要重新定义「珍珠奶茶类」「布丁奶茶类」,而是通过一层层“装饰”来实现。


一、为什么需要装饰模式?

假设你有一个 MilkTea 接口:

public interface MilkTea {String getDescription();double cost();
}

如果用户要点「珍珠奶茶」「布丁奶茶」「珍珠布丁奶茶」,直观的做法是写很多子类:

  • PearlMilkTea

  • PuddingMilkTea

  • PearlAndPuddingMilkTea
    ……

这会导致 类爆炸问题,因为每多一种配料,组合数量就指数级增加。

装饰模式就解决了这个问题:我们不在继承树上不断扩展,而是通过组合(composition)和包装(wrapping)来动态增强对象功能


二、装饰模式的结构

装饰模式有四个核心角色:

  1. Component(组件接口)
    定义对象的抽象接口,比如 MilkTea

  2. ConcreteComponent(具体组件)
    实现接口的基本功能,比如 SimpleMilkTea

  3. Decorator(装饰抽象类)
    持有一个 Component 引用,并且实现相同接口,用来“套娃”。

  4. ConcreteDecorator(具体装饰类)
    在调用被装饰对象的方法基础上,增加新行为,比如 PearlDecorator、PuddingDecorator


三、代码示例(奶茶)

1. 组件接口
public interface MilkTea {String getDescription();double cost();
}2. 具体组件:基础奶茶
public class SimpleMilkTea implements MilkTea {@Overridepublic String getDescription() {return "原味奶茶";}@Overridepublic double cost() {return 8.0; // 基础价格}
}3. 抽象装饰类
public abstract class MilkTeaDecorator implements MilkTea {protected MilkTea milkTea;public MilkTeaDecorator(MilkTea milkTea) {this.milkTea = milkTea;}@Overridepublic String getDescription() {return milkTea.getDescription();}@Overridepublic double cost() {return milkTea.cost();}
}4. 具体装饰:加珍珠
public class PearlDecorator extends MilkTeaDecorator {public PearlDecorator(MilkTea milkTea) {super(milkTea);}@Overridepublic String getDescription() {return super.getDescription() + ", 珍珠";}@Overridepublic double cost() {return super.cost() + 2.0; // 珍珠加价}
}5. 具体装饰:加布丁
public class PuddingDecorator extends MilkTeaDecorator {public PuddingDecorator(MilkTea milkTea) {super(milkTea);}@Overridepublic String getDescription() {return super.getDescription() + ", 布丁";}@Overridepublic double cost() {return super.cost() + 1.0; // 布丁加价}
}6. 使用示例
public class Main {public static void main(String[] args) {MilkTea baseTea = new SimpleMilkTea();System.out.println(baseTea.getDescription() + " => ¥" + baseTea.cost());// 加珍珠MilkTea pearlTea = new PearlDecorator(baseTea);System.out.println(pearlTea.getDescription() + " => ¥" + pearlTea.cost());// 再加布丁MilkTea pearlPuddingTea = new PuddingDecorator(pearlTea);System.out.println(pearlPuddingTea.getDescription() + " => ¥" + pearlPuddingTea.cost());}
}

输出:

原味奶茶 => ¥8.0
原味奶茶, 珍珠 => ¥10.0
原味奶茶, 珍珠, 布丁 => $11.0

小结

SimpleMilkTea → 基础奶茶

PearlDecorator、PuddingDecorator → 装饰器,可以自由叠加

组合灵活,避免写出大量继承类(如 珍珠布丁奶茶、双珍珠奶茶)


四、装饰模式的特点

优点:

  • 灵活:运行时可动态组合功能,而非编译时固定继承。

  • 遵循开闭原则(OCP):不修改原有类,就能增强功能。

  • 可无限层叠组合,比如「奶茶 + 双份珍珠 + 双份布丁」。

缺点:

  • 层数过多时,调试、排查比较麻烦。

  • 对象包装链过长时,可能影响性能。


五、实际应用场景: Java IO 库


InputStreamBufferedInputStreamDataInputStream 就是典型的装饰模式。每一层包装为原始流提供新功能。

1. 结构回顾:装饰模式骨架

  • Component(抽象组件)InputStream 抽象类

  • ConcreteComponent(具体组件)FileInputStreamByteArrayInputStream

  • Decorator(抽象装饰类)FilterInputStream

  • ConcreteDecorator(具体装饰类)BufferedInputStreamDataInputStreamPushbackInputStream


2. 源码入口:InputStream

public abstract class InputStream implements Closeable {public abstract int read() throws IOException;// 还有一些 read(byte[])、skip() 等默认实现
}

这里定义了数据读取的抽象接口,所有输入流都得实现。


3. 被装饰的具体组件:FileInputStream

public class FileInputStream extends InputStream {private final FileDescriptor fd;@Overridepublic int read() throws IOException {return read0();}private native int read0() throws IOException;
}

这是最基础的流,直接从文件里读取字节。
功能很“原始”,没有缓冲、没有数据类型转换。


4. 装饰抽象类:FilterInputStream

public class FilterInputStream extends InputStream {protected volatile InputStream in;protected FilterInputStream(InputStream in) {this.in = in;}@Overridepublic int read() throws IOException {return in.read();  // 委托给被装饰对象}
}

关键点:

  • 它持有一个 InputStream in

  • 所有方法都是转发调用(即套娃)。

  • 它本身不加功能,只是“抽象层”,为子类装饰器铺路。


5. 具体装饰:BufferedInputStream

public class BufferedInputStream extends FilterInputStream {private static int DEFAULT_BUFFER_SIZE = 8192;protected volatile byte buf[];protected int count;protected int pos;public BufferedInputStream(InputStream in) {this(in, DEFAULT_BUFFER_SIZE);}public BufferedInputStream(InputStream in, int size) {super(in);buf = new byte[size];}@Overridepublic int read() throws IOException {if (pos >= count) {fill();  // 从底层 InputStream 批量读取if (pos >= count) return -1;}return buf[pos++] & 0xff;  // 从缓冲区读}
}

这里的增强逻辑是 “缓冲”

  • 底层 FileInputStream 一次只能读一个字节 → 效率低。

  • BufferedInputStream 会一次性把数据读到内存缓冲区,再一个个返回 → 减少系统调用,大幅提升性能。


6. 另一个具体装饰:DataInputStream

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();if ((ch1 | ch2 | ch3 | ch4) < 0)throw new EOFException();return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4));}
}

这里的增强逻辑是 “数据类型解析”

  • 底层 InputStream 只会提供原始字节

  • DataInputStream 能把字节组合成 intlongUTF 字符串 等高级数据类型。


7. 使用示例

InputStream in = new FileInputStream("data.bin");// 加缓冲
InputStream buffered = new BufferedInputStream(in);// 再加数据类型解析
DataInputStream dataIn = new DataInputStream(buffered);int magic = dataIn.readInt();   // 直接读 int
String msg = dataIn.readUTF(); // 直接读 UTF 字符串

调用链路:
FileInputStream → BufferedInputStream → DataInputStream
这就是装饰模式的典型应用:层层包装,动态增强


8. 总结:Java IO 与装饰模式

  1. 继承树避免爆炸:如果要在所有流都支持“缓冲+数据解析”,继承会爆炸(BufferedFileDataInputStream 之类),装饰模式完美解决。

  2. 灵活组合:你可以只用 BufferedInputStream,也可以只用 DataInputStream,也可以两者结合。

  3. 职责分离:每个装饰器只关心自己的增强逻辑,保持单一职责。


六、代理模式 vs 装饰模式

维度代理模式(Proxy)装饰模式(Decorator)
设计意图控制对对象的访问,隔离真实对象动态地给对象添加新功能
客户端视角客户端以为直接在用目标对象,实际经过代理客户端以为直接在用目标对象,实际经过装饰
关注点“能不能访问、如何访问”“功能增强、行为叠加”
典型职责远程代理、虚拟代理、保护代理(如权限检查、延迟加载、远程调用)在不修改类的情况下增强功能(如日志、缓存、加密、IO 缓冲)
实现方式代理对象持有真实对象的引用,并在方法调用时控制调用过程装饰对象持有被装饰对象的引用,并在方法调用前后添加逻辑
行为变化方法结果通常保持一致,只是访问路径受控方法结果通常增强或变化,功能比原来更多
类结构相似度与装饰模式几乎一致与代理模式几乎一致
典型案例Spring AOP(JDK Proxy / CGLIB)、RPC 桩、MyBatis Mapper 动态代理Java IO (BufferedInputStreamDataInputStream)、GUI 组件装饰、日志增强

一句话区分:

  • 代理模式:重点是“拦路虎”——先过我这一关,再去找目标对象。

  • 装饰模式:重点是“打补丁”——原本能做的事还照样能做,但我在周围加了点料。


装饰模式本质是 组合优于继承 的典型实践,用“层层套娃”的方式解决继承树爆炸的问题。

http://www.dtcms.com/a/344293.html

相关文章:

  • 技术革新:再互动平台如何以全链路数字化重构防伪溯源生态
  • 泵站远程监控与自动化控制系统:智慧泵房设备的创新实践
  • RLHF的定义
  • 无人机延时模块技术难点解析
  • 数字安全隐形基石:随机数、熵源与DRBG核心解析与技术关联
  • Kubernetes 构建高可用、高性能 Redis 集群
  • 服务器硬件中的磁盘SSD与HDD性能区别,以及分别适用于什么业务?
  • 高性能、高实时、高安全:如何在飞凌嵌入式i.MX95xx核心板上同时实现?
  • C++ 循环:从入门到精通的深度解析
  • KubeBlocks for MSSQL 高可用实现
  • 云原生(Cloud Native)技术概述
  • 企业级大模型解决方案:架构、落地与代码实现​
  • LeetCode 分类刷题:34. 在排序数组中查找元素的第一个和最后一个位置
  • Unreal Engine APawn 与 ACharacter 比较
  • 开发避坑指南(31):Oracle 11g LISTAGG函数使用陷阱,缺失WITHIN子句解决方案
  • 如何优雅统计知识库文件个数与子集下不同文件夹文件个数
  • Vue3源码reactivity响应式篇之Ref
  • Oracle APEX 经典报表中的Checkbox
  • 期货Level2五档订单簿0.25秒级高频分时及日频历史行情数据使用指南
  • Docker 部署 MySQL 8.0 完整指南:从拉取镜像到配置远程访问
  • 高级SQL优化 | 告别 Hive 中 GROUP BY 的大 KEY 数据倾斜!PawSQL 自适应优化算法详解
  • MsSQL 函数,实现数字转换成人民币大写
  • IDEA基础配置优化指南(中英双版)
  • matlab中随机森林算法的实现
  • AI重塑职业教育:个性化学习计划提效率、VR实操模拟强技能,对接就业新路径
  • 在Excel和WPS表格中如何隐藏单元格的公式
  • 视觉语言对比学习的发展史:从CLIP、BLIP、BLIP2、InstructBLIP(含MiniGPT4的详解)
  • 一分钟了解六通道 CAN(FD) 集线器
  • 第二阶段WinFrom-6:文件对话框,对象的本地保存,序列化与反序列化,CSV文件操作,INI文件读写
  • 【虚拟化】磁盘置备方式的性能损耗对比