带你读《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 期间努力拼凑个人 碎片时间完成了更新啦)。下期要分享的模版方法模式,是我在工作过程中实际开发的应用里,经常看到的一种设 计模式,因此也是非常好奇:这种模式有什么优点和缺点呢?有没有与之相似的设计模式?又有哪些适用的场景? 小伙伴们可以一起来学习讨论呀,我们下期再见哦!


团队介绍


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

相关文章
|
17天前
|
设计模式 存储
「全网最细 + 实战源码案例」设计模式——装饰者模式
装饰者模式(Decorator Pattern)是一种结构型设计模式,通过“包装”现有对象来为其添加额外功能,而无需修改原有代码。它通过创建装饰类来扩展对象的功能,而非继承。该模式由抽象构件、具体构件、抽象装饰者和具体装饰者组成,允许在运行时动态组合功能。穿衣服的例子很好地解释了装饰者模式:你可以根据需要一层层添加衣物,如毛衣、夹克和雨衣,每件衣物都扩展了基本行为,但不是你的一部分,可以随时脱掉。 优点包括灵活性、避免子类爆炸和符合开闭原则;缺点是可能增加复杂性和难以理解。适用于希望在不修改代码的情况下为对象新增行为的场景,尤其当继承难以实现或不可行时。
50 15
|
2月前
|
设计模式 Java Kotlin
Kotlin教程笔记(56) - 改良设计模式 - 装饰者模式
Kotlin教程笔记(56) - 改良设计模式 - 装饰者模式
47 2
|
3月前
|
设计模式 Java Kotlin
Kotlin教程笔记(56) - 改良设计模式 - 装饰者模式
Kotlin教程笔记(56) - 改良设计模式 - 装饰者模式
|
3月前
|
设计模式 Java Kotlin
Kotlin - 改良设计模式 - 装饰者模式
Kotlin - 改良设计模式 - 装饰者模式
35 4
|
3月前
|
设计模式 Java Kotlin
Kotlin教程笔记(56) - 改良设计模式 - 装饰者模式
Kotlin教程笔记(56) - 改良设计模式 - 装饰者模式
|
6月前
|
设计模式 物联网 Android开发
移动应用与系统:探索未来技术的融合之路后端开发中的设计模式探索
【8月更文挑战第21天】随着科技的飞速发展,移动应用和操作系统已经成为我们日常生活中不可或缺的一部分。本文将深入探讨移动应用开发和移动操作系统的相关话题,包括它们的历史、现状以及未来的发展趋势。我们将从移动应用的开发环境、编程语言和工具等方面进行详细的分析,同时也会讨论移动操作系统的特点、优势以及面临的挑战。最后,我们将展望移动应用与系统在未来技术融合中的可能方向和机遇。
141 58
|
4月前
|
设计模式 Java Kotlin
Kotlin教程笔记(56) - 改良设计模式 - 装饰者模式
Kotlin教程笔记(56) - 改良设计模式 - 装饰者模式
49 0
|
4月前
|
设计模式 Java Kotlin
Kotlin 学习笔记- 改良设计模式 - 装饰者模式
Kotlin 学习笔记- 改良设计模式 - 装饰者模式
46 0
|
4月前
|
设计模式 Java Kotlin
Kotlin教程笔记(56) - 改良设计模式 - 装饰者模式
Kotlin教程笔记(56) - 改良设计模式 - 装饰者模式
32 0
|
7月前
|
设计模式 安全 Java
技术成神之路:设计模式(一)单例模式
【7月更文挑战第3天】技术成神之路:设计模式(一)单例模式
53 1

热门文章

最新文章