关于我们

质量为本、客户为根、勇于拼搏、务实创新

< 返回新闻公共列表

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

发布时间:2023-06-28 19:00:32

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

https://developer.leiyu.cn/article/1262381?groupCode=taobaotech



JDK源码赏析


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


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



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


团队介绍


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


/template/Home/leiyu/PC/Static