从 01 开始 从 01 开始
首页
  • 📚 计算机基础

    • 计算机简史
    • 数字电路
    • 计算机组成原理
    • 操作系统
    • Linux
    • 计算机网络
    • 数据库
    • 编程工具
    • 装机
  • 🎨 前端

    • Node
  • JavaSE
  • Java 高级
  • JavaEE

    • 构建、依赖管理
    • Ant
    • Maven
    • 日志框架
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • 环境管理和配置管理-科普篇
    • Servlet
  • Spring

    • Spring基础
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC

    • SpringMVC 基础
  • SpringBoot

    • SpringBoot 基础
  • Windows 使用技巧
  • 手机相关技巧
  • 最全面的输入法教程
  • 最全面的浏览器教程
  • Office
  • 图片类工具
  • 效率类工具
  • 最全面的 RSS 教程
  • 码字工具
  • 各大平台
  • 校招
  • 五险一金
  • 职场规划
  • 关于离职
  • 杂谈
  • 自媒体
  • 📖 读书

    • 读书工具
    • 走进科学
  • 🌍 英语

    • 从零开始学英语
    • 英语兔的相关视频
    • Larry 想做技术大佬的相关视频
  • 🏛️ 政治

    • 反腐
    • GFW
    • 404 内容
    • 审查与自我审查
    • 互联网
    • 战争
    • 读书笔记
  • 💰 经济

    • 关于税
    • 理财
  • 💪 健身

    • 睡眠
    • 皮肤
    • 口腔健康
    • 学会呼吸
    • 健身日志
  • 🏠 其他

    • 驾驶技能
    • 租房与买房
    • 厨艺
  • 电影

    • 电影推荐
  • 电视剧
  • 漫画

    • 漫画软件
    • 漫画推荐
  • 游戏

    • Steam
    • 三国杀
    • 求生之路
  • 小说
  • 关于本站
  • 关于博主
  • 打赏
  • 网站动态
  • 友人帐
  • 从零开始搭建博客
  • 搭建邮件服务器
  • 本站分享
  • 🌈 生活

    • 2022
    • 2023
    • 2024
    • 2025
  • 📇 文章索引

    • 文章分类
    • 文章归档

晓林

程序猿,自由职业者,博主,英语爱好者,健身达人
首页
  • 📚 计算机基础

    • 计算机简史
    • 数字电路
    • 计算机组成原理
    • 操作系统
    • Linux
    • 计算机网络
    • 数据库
    • 编程工具
    • 装机
  • 🎨 前端

    • Node
  • JavaSE
  • Java 高级
  • JavaEE

    • 构建、依赖管理
    • Ant
    • Maven
    • 日志框架
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • 环境管理和配置管理-科普篇
    • Servlet
  • Spring

    • Spring基础
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC

    • SpringMVC 基础
  • SpringBoot

    • SpringBoot 基础
  • Windows 使用技巧
  • 手机相关技巧
  • 最全面的输入法教程
  • 最全面的浏览器教程
  • Office
  • 图片类工具
  • 效率类工具
  • 最全面的 RSS 教程
  • 码字工具
  • 各大平台
  • 校招
  • 五险一金
  • 职场规划
  • 关于离职
  • 杂谈
  • 自媒体
  • 📖 读书

    • 读书工具
    • 走进科学
  • 🌍 英语

    • 从零开始学英语
    • 英语兔的相关视频
    • Larry 想做技术大佬的相关视频
  • 🏛️ 政治

    • 反腐
    • GFW
    • 404 内容
    • 审查与自我审查
    • 互联网
    • 战争
    • 读书笔记
  • 💰 经济

    • 关于税
    • 理财
  • 💪 健身

    • 睡眠
    • 皮肤
    • 口腔健康
    • 学会呼吸
    • 健身日志
  • 🏠 其他

    • 驾驶技能
    • 租房与买房
    • 厨艺
  • 电影

    • 电影推荐
  • 电视剧
  • 漫画

    • 漫画软件
    • 漫画推荐
  • 游戏

    • Steam
    • 三国杀
    • 求生之路
  • 小说
  • 关于本站
  • 关于博主
  • 打赏
  • 网站动态
  • 友人帐
  • 从零开始搭建博客
  • 搭建邮件服务器
  • 本站分享
  • 🌈 生活

    • 2022
    • 2023
    • 2024
    • 2025
  • 📇 文章索引

    • 文章分类
    • 文章归档
  • JavaSE

    • 我的 Java 学习路线
    • 安装 Java
    • Java 数据类型

    • Java 多版本配置
    • 面向对象

    • Java 核心类

    • IO

      • IO 介绍
      • File 对象介绍
      • InputStream
      • OutputStream
      • Decorator 模式
        • 使用装饰器模式之前
        • 装饰器模式
        • 小结
      • 读写 zip 文件
      • Properties 类
      • 读取 classpath 中的配置文件
      • 序列化与反序列化
      • Reader
      • Writer
      • PrintStream 和 PrintWriter
      • Scanner
      • 使用 Files
      • Console
      • IO 系列小结
    • Java 与时间

    • 异常处理

    • 哈希和加密算法

    • Java8 新特性

    • 网络编程

  • JavaSenior

  • JavaEE

  • JavaWeb

  • Spring

  • 主流框架

  • SpringMVC

  • SpringBoot

  • Java
  • JavaSE
  • IO
2023-02-02
目录

Decorator 模式

# Decorator 模式

Decorator 模式,也叫装饰器模式,是为了解决子类数量爆炸的一种设计模式,也叫 Fileter 模式。

# 使用装饰器模式之前

Java 的 IO 标准库提供的 InputStream 根据来源可以包括:

  • FileInputStream:从文件读取数据,是最终数据源;
  • ServletInputStream:从 HTTP 请求读取数据,是最终数据源;
  • Socket.getInputStream():从 TCP 连接读取数据,是最终数据源;
  • ...

如果我们想给这些类添加一些功能,如果使用继承是这样做的:

  • 如果我们要给 FileInputStream 添加缓冲功能,则可以从 FileInputStream 派生一个类:BufferedFileInputStream extends FileInputStream
  • 如果要给 FileInputStream 添加计算签名的功能,也可以从 FileInputStream 派生一个类:DigestFileInputStream extendsFileInputStream
  • 如果要给 FileInputStream 添加加密/解密功能,还是可以从 FileInputStream 派生一个类:CipherFileInputStream extends FileInputStream

这还只是添加了一个功能的情况,如果我们要给 FileInputStream 添加两个功能,还要再派生几个类;添加 3 个功能,还要派生类。这 3 种功能的组合,又需要更多的子类:

                          ┌─────────────────┐
                          │ FileInputStream │
                          └─────────────────┘
                                   ▲
             ┌───────────┬─────────┼─────────┬───────────┐
             │           │         │         │           │
┌───────────────────────┐│┌─────────────────┐│┌─────────────────────┐
│BufferedFileInputStream│││DigestInputStream│││CipherFileInputStream│
└───────────────────────┘│└─────────────────┘│└─────────────────────┘
                         │                   │
    ┌─────────────────────────────┐ ┌─────────────────────────────┐
    │BufferedDigestFileInputStream│ │BufferedCipherFileInputStream│
    └─────────────────────────────┘ └─────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13

这还只是针对 FileInputStream 设计,如果针对另一种 InputStream 设计,很快会出现子类爆炸的情况。

因此,直接使用继承,为各种 InputStream 附加更多的功能,根本无法控制代码的复杂度,很快就会失控。

# 装饰器模式

为了解决子类数量失控的问题,JDK 使用了一种设计模式:装饰器模式。如果不知道这个设计模式的读者可参考下这牌博客:漫画设计模式:什么是 “装饰器模式” ? (opens new window)

关键在于,如果我们想要给一个类加一些功能,不一定要使用继承,也可以用组合的方式:例如我想给 InputStream 加个功能,我可以定义一个类 A,里面有个成员变量 InputStream,然后我在这个类里加上功能相关的代码!这样就不用派生类,也可以实现这个效果。因为所有输入流都是继承自 InputStream 的,所以类 A 也可以继承自 InputStream,这样它就可以被当成一个输入流。

如果想给类 A 加个功能,一样的道理,定义一个类 B,里面有个成员变量类 InputStream(因为都是继承自 InputStream,所以可以用 InputStream 来接受变量),然后类 B 里面加上功能代码。

举个贴近生活的例子:

上班族大多都有睡懒觉的习惯,每天早上上班时间都很紧张,于是很多人为了多睡一会,就会用方便的方式解决早餐问题。有些人早餐可能会吃煎饼,煎饼中可以加鸡蛋,也可以加香肠,但是不管怎么“加码”,都还是一个煎饼。在现实生活中,常常需要对现有产品增加新的功能或美化其外观,如房子装修、相片加相框等,都是装饰器模式。

在软件开发过程中,有时想用一些现存的组件。这些组件可能只是完成了一些核心功能。但在不改变其结构的情况下,可以动态地扩展其功能。所有这些都可以釆用装饰器模式来实现。

--24 设计模式——装饰器模式(装饰设计模式)详解 - 知乎 (opens new window)

以 JDK 为例,JDK 是这样设计的:

首先将 InputStream 分为两大类:

一类是直接提供数据的基础 InputStream,例如:

  • FileInputStream
  • ByteArrayInputStream
  • ServletInputStream
  • ...

一类是提供额外附加功能的 InputStream,例如:

  • BufferedInputStream
  • DigestInputStream
  • CipherInputStream
  • ...

当我们需要给一个“基础”InputStream 附加各种功能时,我们先确定这个能提供数据源的 InputStream,因为我们需要的数据总得来自某个地方,例如,FileInputStream,数据来源自文件:

InputStream file = new FileInputStream("test.gz");
1

紧接着,我们希望 FileInputStream 能提供缓冲的功能来提高读取的效率,因此我们用 BufferedInputStream 包装这个 InputStream,得到的包装类型是 BufferedInputStream,但它仍然被视为一个 InputStream:

InputStream buffered = new BufferedInputStream(file);
1

最后,假设该文件已经用 gzip 压缩了,我们希望直接读取解压缩的内容,就可以再包装一个 GZIPInputStream:

InputStream gzip = new GZIPInputStream(buffered);
1

无论我们包装多少次,得到的对象始终是 InputStream,我们直接用 InputStream 来引用它,就可以正常读取:

┌─────────────────────────┐
│GZIPInputStream          │
│┌───────────────────────┐│
││BufferedFileInputStream││
││┌─────────────────────┐││
│││   FileInputStream   │││
││└─────────────────────┘││
│└───────────────────────┘│
└─────────────────────────┘
1
2
3
4
5
6
7
8
9

上述这种通过一个“基础”组件再叠加各种“附加”功能组件的模式,称之为 Filter 模式(或者装饰器模式:Decorator)。它可以让我们通过少量的类来实现各种功能的组合:

                 ┌─────────────┐
                 │ InputStream │
                 └─────────────┘
                       ▲ ▲
┌────────────────────┐ │ │ ┌─────────────────┐
│  FileInputStream   │─┤ └─│FilterInputStream│
└────────────────────┘ │   └─────────────────┘
┌────────────────────┐ │     ▲ ┌───────────────────┐
│ByteArrayInputStream│─┤     ├─│BufferedInputStream│
└────────────────────┘ │     │ └───────────────────┘
┌────────────────────┐ │     │ ┌───────────────────┐
│ ServletInputStream │─┘     ├─│  DataInputStream  │
└────────────────────┘       │ └───────────────────┘
                             │ ┌───────────────────┐
                             └─│CheckedInputStream │
                               └───────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

类似的,OutputStream 也是以这种模式来提供各种功能。

注意到在叠加多个 FilterInputStream,我们只需要持有最外层的 InputStream,并且,当最外层的 InputStream关闭时(在 try(resource)块的结束处自动关闭),内层的 InputStream的 close()方法也会被自动调用,并最终调用到最核心的“基础”InputStream,因此不存在资源泄露。

# 小结

Decorator 模式可以在运行期动态增加功能,Java 的 IO 标准库使用装饰器模式为 InputStream 和 OutputStream 增加功能:

  • 可以把一个 InputStream和任意个 FilterInputStream组合;
  • 可以把一个 OutputStream和任意个 FilterOutputStream组合。
  • 当最外层的 InputStream 关闭时,内层的 InputStream 也会被关闭,不用担心资源泄漏的问题。

推荐阅读

  • 漫画设计模式:什么是 “装饰器模式” ? (opens new window):小灰的博客,对于装饰器模式讲解的非常好
  • 装饰器模式 | 菜鸟教程 (opens new window)
上次更新: 2025/6/3 09:31:54
OutputStream
读写 zip 文件

← OutputStream 读写 zip 文件→

最近更新
01
语雀文档一键下载至本地教程
07-04
02
要成功,就不要低估环境对你的影响
07-03
03
血泪教训:电子设备要定期开机
07-02
更多文章>
Theme by Vdoing | Copyright © 2022-2025 | 粤 ICP 备 2022067627 号 -1 | 粤公网安备 44011302003646 号 | 点击查看十年之约
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式