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


团队介绍


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

相关文章
|
13天前
|
设计模式 前端开发 JavaScript
【JavaScript 技术专栏】JavaScript 设计模式与实战应用
【4月更文挑战第30天】本文探讨JavaScript设计模式在提升开发效率和代码质量中的关键作用。涵盖单例、工厂、观察者、装饰器和策略模式,并通过实例阐述其在全局状态管理、复杂对象创建、实时数据更新、功能扩展和算法切换的应用。理解并运用这些模式能帮助开发者应对复杂项目,提升前端开发能力。
|
4月前
|
设计模式 数据库连接 数据库
发挥设计模式单例模式的力量:从技术到社会的转变
发挥设计模式单例模式的力量:从技术到社会的转变
|
2月前
|
设计模式 安全 Java
【分布式技术专题】「Tomcat技术专题」 探索Tomcat技术架构设计模式的奥秘(Server和Service组件原理分析)
【分布式技术专题】「Tomcat技术专题」 探索Tomcat技术架构设计模式的奥秘(Server和Service组件原理分析)
35 0
|
5月前
|
设计模式 Java
Java设计模式【十】:装饰者模式
Java设计模式【十】:装饰者模式
23 0
|
2天前
|
设计模式
LabVIEW状态机设计模式技术手册和练习
LabVIEW状态机设计模式技术手册和练习
|
19天前
|
设计模式 存储 前端开发
Java从入门到精通:2.2.1学习Java Web开发,了解Servlet和JSP技术,掌握MVC设计模式
Java从入门到精通:2.2.1学习Java Web开发,了解Servlet和JSP技术,掌握MVC设计模式
|
21天前
|
设计模式 Java
【设计模式系列笔记】装饰者模式
装饰者模式是一种结构型设计模式,它允许你通过将对象放入包含行为的特殊封装类中来为原始对象添加新的行为。这种模式可以动态地将责任附加到对象上,而不影响其它对象。
38 11
|
3月前
|
设计模式 缓存 安全
设计模式-代理模式(静态代理、动态代理、cglib代理)、代理模式和装饰者模式的区别
设计模式-代理模式(静态代理、动态代理、cglib代理)、代理模式和装饰者模式的区别
|
4月前
|
设计模式 Go 开发工具
Golang设计模式——22装饰者模式
Golang设计模式——22装饰者模式
25 0
|
5月前
|
Java 应用服务中间件 数据库连接
太厉害!Redis+Nginx+设计模式+Spring全家桶+Dubbo技术精选合集
最近花了很长的时间去搜罗Java核心技术好文,我把每个Java核心技术的优选文章都整理成了一个又一个的文档。昨天也是终于全部整理好了,今天就把这些东西分享给老铁们,也能为老铁们省去不少麻烦,想学什么技能了,遇到哪方面的问题了 直接打开文档学一学就好了。不多bb了,直接上干货!