带你读《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 提供输入流过滤功能,可以跟踪输入流中的行号(以回车符、换行符标记换行)。
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 期间努力拼凑个人 碎片时间完成了更新啦)。下期要分享的模版方法模式,是我在工作过程中实际开发的应用里,经常看到的一种设 计模式,因此也是非常好奇:这种模式有什么优点和缺点呢?有没有与之相似的设计模式?又有哪些适用的场景? 小伙伴们可以一起来学习讨论呀,我们下期再见哦!
团队介绍
我们聚焦优惠和选购体验, 通过数智化驱动形成更有效率和确定性的货品运营方法论,为消费者提供精选和极致性价比的商品,为商家提供更 具爆发确定性的营销方案。