【Java设计模式 设计模式与范式】结构型模式 三:装饰器模式(下)

简介: 【Java设计模式 设计模式与范式】结构型模式 三:装饰器模式

2 基于装饰器模式设计Java IO

如果基于装饰器模式设计Java IO就会很灵活,需要什么功能就去装饰什么功能,还可以实现嵌套装饰

抽象组件

public abstract class InputStream {
  //...
  public int read(byte b[]) throws IOException {
    return read(b, 0, b.length);
  }
  public int read(byte b[], int off, int len) throws IOException {
    //...
  }
  public long skip(long n) throws IOException {
    //...
  }
//其它方法不详细展开
}

具体组件

class FileInputStream extends InputStream
{
  public FileInputStream(String name) throws FileNotFoundException {
        this(name != null ? new File(name) : null);
    }
    public FileInputStream(File file) throws FileNotFoundException {
        String name = (file != null ? file.getPath() : null);
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkRead(name);
        }
        if (name == null) {
            throw new NullPointerException();
        }
        if (file.isInvalid()) {
            throw new FileNotFoundException("Invalid file path");
        }
        fd = new FileDescriptor();
        fd.attach(this);
        path = name;
        open(name);
    }
    public int read() throws IOException {
        return read0();
    }
    private native int read0() throws IOException;
    private native int readBytes(byte b[], int off, int len) throws IOException;
    public int read(byte b[]) throws IOException {
        return readBytes(b, 0, b.length);
    }
    public int read(byte b[], int off, int len) throws IOException {
        return readBytes(b, off, len);
    }
//其它方法不详细展开
}

抽象装饰器

public class FilterInputStream extends InputStream {
  protected volatile InputStream in;
  protected FilterInputStream(InputStream in) {
    this.in = in;
  }
  public int read() throws IOException {
    return in.read();
  }
  public int read(byte b[]) throws IOException {
    return read(b, 0, b.length);
  }
  public int read(byte b[], int off, int len) throws IOException {
    return in.read(b, off, len);
  }
  public long skip(long n) throws IOException {
    return in.skip(n);
  }
  public int available() throws IOException {
    return in.available();
  }
  public void close() throws IOException {
    in.close();
  }
  public synchronized void mark(int readlimit) {
    in.mark(readlimit);
  }
  public synchronized void reset() throws IOException {
    in.reset();
  }
  public boolean markSupported() {
    return in.markSupported();
  }
}

为什么需要FilterInputStream这个抽象装饰器呢?

  1. InputStream 是一个抽象类而非接口,而且它的大部分函数(比如 read()、available())都有默认实现,理论上只需要在 BufferedInputStream 类中重新实现那些需要增加缓存功能的函数就可以了,其他函数继承 InputStream 的默认实现。但实际上,这样做是行不通的,对于即便是不需要增加缓存功能的函数来说,BufferedInputStream 还是必须把它重新实现一遍,简单包裹对 InputStream 对象的函数调用。如果不重新实现,那 BufferedInputStream 类就无法将最终读取数据的任务,委托给传递进来的 InputStream 对象来完成,因为构造方法的参数类型是个抽象类,那传递进来的就可能是它的子类,运行时同样的方法名不见得用的是同一个方法实现,如果不委托,那么运行的永远是抽象类的方法实现而不能使用其子类的增强实现了。
  2. 你可能会问既然我不增强这个方法用默认的抽象方法不行么?如果是单层装饰这样看起来没问题,单层装饰我们能明确自己要增强的是哪个方法。但如果是嵌套装饰呢?new CheeseDecorator(new CreamDecorator(cakeMaker))CheeseDecorator装饰了CreamDecorator,被装饰者CreamDecorator其实也是一个装饰者,CheeseDecoratorCreamDecorator如果增强的方法不完全一致,例如它们共同增强了cakeMake方法,这也是嵌套装饰的目的。但是CheeseDecorator的方法fun()没有增强,CreamDecorator进行了增强,CheeseDecorator如果不对fun()进行委托实现,那么其实就丢掉了被装饰类CreamDecoratorfun()的增强,这样是不合理的。
  3. 综合以上,又为了避免代码重复,Java IO 抽象出了一个装饰器父类 FilterInputStream。InputStream 的所有的装饰器类(BufferedInputStream、DataInputStream)都继承自这个装饰器父类。这样,装饰器类只需要实现它需要增强的方法就可以了,其他方法继承装饰器父类的默认实现

说白了这个装饰器父类是用来做调用传递转发功能的,让装饰器子类能更聚焦于自己增强的功能。

具体装饰器

public class BufferedInputStream extends FilterInputStream{
  protected volatile InputStream in;
  public BufferedInputStream(InputStream in, int size) {
        super(in);
        if (size <= 0) {
            throw new IllegalArgumentException("Buffer size <= 0");
        }
        buf = new byte[size];
    }
  public BufferedInputStream(InputStream in) {
        this(in, DEFAULT_BUFFER_SIZE);
    }
  public BufferedInputStream(InputStream in, int size) {
        super(in);
        if (size <= 0) {
            throw new IllegalArgumentException("Buffer size <= 0");
        }
        buf = new byte[size];
    }
 }
 //其它方法不详细展开
}
public class DataInputStream extends FilterInputStream implements DataInput {
    public DataInputStream(InputStream in) {
        super(in);
    }
//其它方法不详细展开
}

3 Java IO装饰器模式实现源码分析

客户端调用代码如下:

//它既支持缓存读取,又支持按照基本数据类型来读取数据
InputStream in = new FileInputStream("/user/test.txt");
InputStream bin = new BufferedInputStream(in);
DataInputStream din = new DataInputStream(bin);
int data = din.readInt();

客户端调用时首先执行din.readInt方法:

public final int readInt() throws IOException {
        int ch1 = in.read();
        int ch2 = in.read();
        int ch3 = in.read();
        int ch4 = in.read();
        if ((ch1 | ch2 | ch3 | ch4) < 0)
            throw new EOFException();
        return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0));
    }

这里方法内部使用了in.read(),这个in就来自被装饰的对象:bin

class DataInputStream extends FilterInputStream implements DataInput {
    /**
     * Creates a DataInputStream that uses the specified
     * underlying InputStream.
     *
     * @param  in   the specified input stream
     */
    public DataInputStream(InputStream in) {
        super(in);
    }
}

所以执行的是BufferedInputStreamread方法,通过缓存读取数据:

public synchronized int read() throws IOException {
        if (pos >= count) {
            fill();
            if (pos >= count)
                return -1;
        }
        return getBufIfOpen()[pos++] & 0xff;
    }

这里使用了fill()方法,fill方法的执行内容如下:

private void fill() throws IOException {
        byte[] buffer = getBufIfOpen();
        if (markpos < 0)
            pos = 0;            /* no mark: throw away the buffer */
        else if (pos >= buffer.length)  /* no room left in buffer */
            if (markpos > 0) {  /* can throw away early part of the buffer */
                int sz = pos - markpos;
                System.arraycopy(buffer, markpos, buffer, 0, sz);
                pos = sz;
                markpos = 0;
            } else if (buffer.length >= marklimit) {
                markpos = -1;   /* buffer got too big, invalidate mark */
                pos = 0;        /* drop buffer contents */
            } else if (buffer.length >= MAX_BUFFER_SIZE) {
                throw new OutOfMemoryError("Required array size too large");
            } else {            /* grow buffer */
                int nsz = (pos <= MAX_BUFFER_SIZE - pos) ?
                        pos * 2 : MAX_BUFFER_SIZE;
                if (nsz > marklimit)
                    nsz = marklimit;
                byte nbuf[] = new byte[nsz];
                System.arraycopy(buffer, 0, nbuf, 0, pos);
                if (!bufUpdater.compareAndSet(this, buffer, nbuf)) {
                    // Can't replace buf if there was an async close.
                    // Note: This would need to be changed if fill()
                    // is ever made accessible to multiple threads.
                    // But for now, the only way CAS can fail is via close.
                    // assert buf == null;
                    throw new IOException("Stream closed");
                }
                buffer = nbuf;
            }
        count = pos;
        int n = getInIfOpen().read(buffer, pos, buffer.length - pos);
        if (n > 0)
            count = n + pos;
    }

我们继续向下跟看到一个getInIfOpen().read(buffer, pos, buffer.length - pos)调用,我们看看getInIfOpen方法:

private InputStream getInIfOpen() throws IOException {
        InputStream input = in;
        if (input == null)
            throw new IOException("Stream closed");
        return input;
    }

这里用的in对象就来自于FileInputStream对象,在new BufferedInputStream时候将构造函数传递的in赋给了成员变量in

public BufferedInputStream(InputStream in) {
        this(in, DEFAULT_BUFFER_SIZE);
    }
public BufferedInputStream(InputStream in, int size) {
        super(in);
        if (size <= 0) {
            throw new IllegalArgumentException("Buffer size <= 0");
        }
        buf = new byte[size];
    }

super(in)其实就是调用了原始的FilterInputStream 的构造函数,将参数in赋予成员变量in

class FilterInputStream extends InputStream {
    protected volatile InputStream in;
    protected FilterInputStream(InputStream in) {
        this.in = in;
    }
}

这样就通过双重装饰,将普通的文件流改装为可以进行缓存读和基础数据读的文件流。

模式对比

这里我们比较下装饰器模式和代理模式:

代理模式和装饰器模式

让别人帮助你做你并不关心的事情,叫代理模式;为让自己的能力增强,使得增强后的自己能够使用更多的方法,拓展在自己基础之上的功能的,叫装饰器模式

对装饰器模式来说,装饰者(decorator)和被装饰者(decoratee)都实现同一个 接口。对代理模式来说,代理类(proxy class)和真实处理的类(real class)都实现同一个接口。他们之间的边界确实比较模糊,两者都是对类的方法进行扩展,具体区别如下:

  1. 装饰器模式强调的是增强自身,在被装饰之后你能够在被增强的类上使用增强后的功能。增强后你还是你,只不过能力更强了而已;代理模式强调要让别人帮你去做一些本身与你业务没有太多关系的职责(记录日志、设置缓存)。代理模式是为了实现对象的控制,因为被代理的对象往往难以直接获得或者是其内部不想暴露出来。
  2. 装饰模式是以对客户端透明的方式扩展对象的功能,是继承方案的一个替代方案;代理模式则是给一个对象提供一个代理对象,并由代理对象来控制对原有对象的引用;
  3. 装饰模式是为装饰的对象增强功能;而代理模式对代理的对象施加控制,但不对对象本身的功能进行增强;

核心的区别可以这么理解:装饰器模式是增强原有对象功能,是纵向继承的一个代替方案。代理模式是在原有对象的功能前后进行一些业务无关的AOP处理,例如记录日志等,简而言之,一个纵向,一个横向

模式扩展

装饰器模式所包含的 4 个角色不是任何时候都要存在的,在有些应用环境下模式是可以简化的,如以下两种情况:

如果只有一个具体组件而没有抽象组件时,可以让抽象装饰继承具体组件

如果只有一个具体装饰器,可以将抽象装饰器和具体装饰器合并

这种结构就非常像代理模式了,唯一的区别就是具体装饰持有的引用是抽象组件,而不是像代理对象持有的是具体目标对象的引用而已。这就引出了它们非常大的一个区别:装饰模式的功能实现是由上层调用者决定的,而代理模式的功能已经写死了,不能够实现灵活的拓展,从结构上甚至可以简单的理解为装饰模式是代理模式的超集,但从使用角度看完全是两回事儿。

总结一下

装饰器模式和代理模式其实很容易混了,说实在的单从角色关系结构上来说也非常相似。但是我们从使用角度出发去看还是有很多差别的,代理模式解决的问题是对当前功能的横向扩展,所以每个代理类要横向扩展的功能是固定的,例如增加日志,所以设计上不存在也没有必要定义抽象代理类,横向功能是不需要也不可能穷尽收束的。而装饰者模式被设计来是用来替代继承实现类功能增强的一种方案,设计上存在抽象装饰类,以便通过各种具体装饰子类对具体组件实现多样的功能增强,对一个具体组件来说我们是可以给它定制装饰方案的,所以通过抽象装饰类来实现。

相关文章
|
2月前
|
设计模式 XML Java
【设计模式】装饰器模式(定义 | 特点 | Demo入门讲解)
【设计模式】装饰器模式(定义 | 特点 | Demo入门讲解)
38 0
|
4天前
|
设计模式 前端开发 JavaScript
前端必须掌握的设计模式——装饰器模式
装饰器模式是一种结构型设计模式,通过创建新类来包装原始对象,实现在不修改原有结构的前提下扩展新行为。其核心在于“组合”思想,使新功能可“即插即拔”。该模式具有解耦性、灵活性和动态性等特点,广泛应用于类的面向对象编程语言中,如JavaScript的注解和TypeScript的写法。示例中,通过装饰器模式为游戏角色动态添加装备,展示了其强大的扩展性和灵活性。
|
16天前
|
存储 Java 数据挖掘
Java 8 新特性之 Stream API:函数式编程风格的数据处理范式
Java 8 引入的 Stream API 提供了一种新的数据处理方式,支持函数式编程风格,能够高效、简洁地处理集合数据,实现过滤、映射、聚合等操作。
33 5
|
17天前
|
设计模式 消息中间件 搜索推荐
Java 设计模式——观察者模式:从优衣库不使用新疆棉事件看系统的动态响应
【11月更文挑战第17天】观察者模式是一种行为设计模式,定义了一对多的依赖关系,使多个观察者对象能直接监听并响应某一主题对象的状态变化。本文介绍了观察者模式的基本概念、商业系统中的应用实例,如优衣库事件中各相关方的动态响应,以及模式的优势和实际系统设计中的应用建议,包括事件驱动架构和消息队列的使用。
|
28天前
|
设计模式 Java 数据库连接
Java编程中的设计模式:单例模式的深度剖析
【10月更文挑战第41天】本文深入探讨了Java中广泛使用的单例设计模式,旨在通过简明扼要的语言和实际示例,帮助读者理解其核心原理和应用。文章将介绍单例模式的重要性、实现方式以及在实际应用中如何优雅地处理多线程问题。
32 4
|
2月前
|
设计模式 Java 程序员
[Java]23种设计模式
本文介绍了设计模式的概念及其七大原则,强调了设计模式在提高代码重用性、可读性、可扩展性和可靠性方面的作用。文章还简要概述了23种设计模式,并提供了进一步学习的资源链接。
49 0
[Java]23种设计模式
|
1月前
|
设计模式 JavaScript Java
Java设计模式:建造者模式详解
建造者模式是一种创建型设计模式,通过将复杂对象的构建过程与表示分离,使得相同的构建过程可以创建不同的表示。本文详细介绍了建造者模式的原理、背景、应用场景及实际Demo,帮助读者更好地理解和应用这一模式。
|
2月前
|
设计模式 Java
Java设计模式
Java设计模式
32 0
|
设计模式 Java
【Java设计模式 设计模式与范式】结构型模式 一:适配器模式
【Java设计模式 设计模式与范式】结构型模式 一:适配器模式
62 0
|
设计模式 Java
【玩转23种Java设计模式】结构型模式篇:适配器模式
适配器模式(Adapter Pattern)将某个类的接口转换成客户端期望的另一个接口表示,主的目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同工作。适配器模式属于结构型模式,主要分为三类:类适配器模式、对象适配器模式、接口适配器模式。
【玩转23种Java设计模式】结构型模式篇:适配器模式