带你读《2022技术人的百宝黑皮书》——浅析设计模式3 —— 装饰者模式(3)

简介: 带你读《2022技术人的百宝黑皮书》——浅析设计模式3 —— 装饰者模式

带你读《2022技术人的百宝黑皮书》——浅析设计模式3 —— 装饰者模式(2)

https://developer.aliyun.com/article/1262381?groupCode=taobaotech



JDK源码赏析


Java I/O标准库是装饰者模式在Java语言中非常经典的应用实例。


如下图所示,InputStream 相当于抽象构件,FilterInputStream 类似于抽象装饰者,它的四个子类等同于具体装 饰者。其中,FilterInputStream 中含有被装饰类 InputStream 的引用,其具体装饰者及各自功能为:PushbackInputStream 能弹出一个字节的缓冲区,可将输入流放到回退流中;DataInputStream 与 DataOutputStream搭 配使用,用来装饰其它输入流,允许应用程序以一种与机器无关的方式从底层输入流中读取基本 Java 数据类型;BufferedInputStream 使用缓冲数组提供缓冲输入流功能,在每次调用 read 方法时优先从缓冲区读取数据,比 直接从物理数据源读取数据的速度更快;LineNumberInputStream 提供输入流过滤功能,可以跟踪输入流中的行号(以回车符、换行符标记换行)。


image.png


FilterInputStream 是所有装饰器类的抽象类,提供特殊的输入流控制。下面源码省略了 skip、available、mark、 reset、markSupported 方法,这些方法也都委托给了 InputStream 类。其中, InputStream 提供装饰器类的接口,因而此类并没有对 InputStream 的功能做任何扩展,其扩展主要交给其子类来实现。

public class FilterInputStream extends InputStream {
 //维护一个 InputStream 对象
 protected volatile InputStream in; 
  //构造方法参数需要一个 inputStream
 protected FilterInputStream(InputStream in) {
 this.in = in;
 }
  //委托给 InputStream
 public int read() throws IOException {
 return in.read();
 }
  //委托给 InputStream
 public void close() throws IOException {
 in.close();
 }
 .......
 }


由于源码太长,这里先以 PushbackInputStream 为例,展示 FilterInputStream 的具体装饰者的底层实现,大家感兴趣的话可以自行查阅其它源码哦。PushbackInputStream 内部维护了一个 pushback buf 缓冲区,可以帮助我们试探性地读取数据流,对于不想要的数据也可以返还回去。

public class PushbackInputStream extends FilterInputStream {
 //缓冲区
 protected byte[] buf; 
 protected int pos;
 private void ensureOpen() throws IOException {
 if (in == null)
 throw new IOException("Stream closed");
 }
 //构造函数可以指定返回的字节个数
 public PushbackInputStream(InputStream in, int size) { 
  super(in);
 if (size <= 0) {
 throw new IllegalArgumentException("size <= 0");
 }
 //初始化缓冲区的大小
 this.buf = new byte[size];
 //设置读取的位置
 this.pos = size;
 }
 //默认回退一个
 public PushbackInputStream(InputStream in) {
 this(in, 1);
 }
  public int read() throws IOException {
 //确保流存在
 ensureOpen();
 //如果要读取的位置在缓冲区里面
 if (pos < buf.length) {
 //返回缓冲区中的内容
 return buf[pos++] & 0xff;
 }
 //否则调用超类的读函数
 return super.read();
 }
  //读取指定的长度
 public int read(byte[] b, int off, int len) throws IOException {
 ensureOpen();
 if (b == null) {
 throw new NullPointerException();
 } else if (off < 0 || len < 0 || len > b.length - off) {
 throw new IndexOutOfBoundsException();
 } else if (len == 0) {
 return 0;
 }
 //缓冲区长度减去读取位置
 int avail = buf.length - pos;
 //如果大于0,表明部分数据可以从缓冲区读取
 if (avail > 0) {
 //如果要读取的长度小于可从缓冲区读取的字符
if (len < avail) {
 //修改可读取值为实际要读的长度
 avail = len;
 }
 //将buf中的数据复制到b中
 System.arraycopy(buf, pos, b, off, avail);
 //修改pos的值
 pos += avail;
 //修改off偏移量的值
 off += avail;
 //修改len的值
 len -= avail;
 }
 //如果从缓冲区读取的数据不够
 if (len > 0) {
 //从流中读取
 len = super.read(b, off, len);
 if (len == -1) {
 return avail == 0 ? -1 : avail;
 }
 return avail + len;
 }
 return avail;
 }
 //不读字符b
 public void unread(int b) throws IOException {
 ensureOpen();
 if (pos == 0) {
 throw new IOException("Push back buffer is full");
 }
 //实际就是修改缓冲区中的值,同时pos后退
 buf[--pos] = (byte)b;
 }
  public void unread(byte[] b, int off, int len) throws IOException {
 ensureOpen();
 if (len > pos) {
 throw new IOException("Push back buffer is full");
 }
 //修改缓冲区中的值,pos后退多个
  pos -= len;
 System.arraycopy(b, off, buf, pos, len);
 }
 public void unread(byte[] b) throws IOException {
 unread(b, 0, b.length);
 }
}


优缺点及适用场景


优点

1. 提供比继承更加灵活的扩展功能,通过叠加不同的具体装饰者的方法,动态地增强目标类的功能。

2. 装饰者和被装饰者可以独立发展,不会相互耦合,比如说我们想再加一个炒河粉只需创建一个炒河粉类继承  FastFood即可,而想要增加火腿肠配料就增加一个类去继承 Garnish 抽象装饰者。


缺点

使用装饰模式,可以比使用继承关系创建更少的类,使设计比较易于进行。然而,多层装饰会产生比继承更多的对 象,使查错更加困难,尤其是这些对象都很相似。而且,当目标类被多次动态装饰后,程序的复杂性也会大大提 升,难以维护。


适用场景

1. 继承关系不利于系统维护,甚至不能使用继承关系的场景。比如,当继承导致类爆炸时、目标类被 final 修饰时,都不宜通过创建目标类的子类来扩展功能。

2. 要求不影响其他对象,为特定目标对象添加功能。

3. 要求动态添加、撤销对象的功能。


总结


装饰者模式也是一种比较容易理解和上手的设计模式,它可以对多个装饰者类进行花式排列组合,适应多变的用户需求。同时,装饰者模式也是符合开闭原则的,被装饰的对象和装饰者类互相独立、互不干扰。


在介绍装饰者模式的适用场景时,我们可以发现上述场景在实际工程中也比较常见,因此装饰者模式同样应用广 泛。除了本文提到的 Java I/O,装饰者模式的典型应用实例还有:Spring cache 中的 TransactionAwareCacheDecorator 类、 Spring session 中的 ServletRequestWrapper 类、Mybatis 缓存中的 decorators 包等等。


下篇预告:现在,我们已经学习了三种设计模式,涉及了创建型模式、行为型模式和结构型模式。而下一篇内容其 实早在写装饰者模式之前就已经确定了主题 —— 模版方法模式,只不过为了承接第二篇文章最后的 “下篇内容预 告” 环节,这段时间还是先行研究了结构型模式 (这次文章出的比较慢,但也算是在备战双 11 期间努力拼凑个人 碎片时间完成了更新啦)。下期要分享的模版方法模式,是我在工作过程中实际开发的应用里,经常看到的一种设 计模式,因此也是非常好奇:这种模式有什么优点和缺点呢?有没有与之相似的设计模式?又有哪些适用的场景? 小伙伴们可以一起来学习讨论呀,我们下期再见哦!


团队介绍


我们聚焦优惠和选购体验, 通过数智化驱动形成更有效率和确定性的货品运营方法论,为消费者提供精选和极致性价比的商品,为商家提供更 具爆发确定性的营销方案。

相关文章
|
9月前
|
设计模式 缓存 安全
【设计模式】【结构型模式】装饰者模式(Decorator)
一、入门 什么是装饰者模式? 装饰者模式(Decorator Pattern)是 Java 中常用的结构型设计模式,它能在不修改原有对象结构的前提下,动态地为对象添加额外的职责。 为什么要装饰者模式?
243 8
|
11月前
|
设计模式 Java 数据安全/隐私保护
Java 设计模式:装饰者模式(Decorator Pattern)
装饰者模式属于结构型设计模式,允许通过动态包装对象的方式为对象添加新功能,提供比继承更灵活的扩展方式。该模式通过组合替代继承,遵循开闭原则(对扩展开放,对修改关闭)。
|
设计模式 Java Kotlin
Kotlin教程笔记(56) - 改良设计模式 - 装饰者模式
Kotlin教程笔记(56) - 改良设计模式 - 装饰者模式
179 2
|
设计模式 Java Kotlin
Kotlin教程笔记(56) - 改良设计模式 - 装饰者模式
Kotlin教程笔记(56) - 改良设计模式 - 装饰者模式
|
设计模式 Java Kotlin
Kotlin - 改良设计模式 - 装饰者模式
Kotlin - 改良设计模式 - 装饰者模式
115 4
|
设计模式 Java Kotlin
Kotlin教程笔记(56) - 改良设计模式 - 装饰者模式
Kotlin教程笔记(56) - 改良设计模式 - 装饰者模式
|
设计模式 Java Kotlin
Kotlin教程笔记(56) - 改良设计模式 - 装饰者模式
Kotlin教程笔记(56) - 改良设计模式 - 装饰者模式
118 0
|
设计模式 Java Kotlin
Kotlin 学习笔记- 改良设计模式 - 装饰者模式
Kotlin 学习笔记- 改良设计模式 - 装饰者模式
143 0
|
设计模式 Java Kotlin
Kotlin教程笔记(56) - 改良设计模式 - 装饰者模式
Kotlin教程笔记(56) - 改良设计模式 - 装饰者模式
82 0