【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月前
|
设计模式 算法 搜索推荐
Java 设计模式之策略模式:灵活切换算法的艺术
策略模式通过封装不同算法并实现灵活切换,将算法与使用解耦。以支付为例,微信、支付宝等支付方式作为独立策略,购物车根据选择调用对应支付逻辑,提升代码可维护性与扩展性,避免冗长条件判断,符合开闭原则。
342 35
|
2月前
|
设计模式 消息中间件 传感器
Java 设计模式之观察者模式:构建松耦合的事件响应系统
观察者模式是Java中常用的行为型设计模式,用于构建松耦合的事件响应系统。当一个对象状态改变时,所有依赖它的观察者将自动收到通知并更新。该模式通过抽象耦合实现发布-订阅机制,广泛应用于GUI事件处理、消息通知、数据监控等场景,具有良好的可扩展性和维护性。
275 8
|
2月前
|
设计模式 网络协议 数据可视化
Java 设计模式之状态模式:让对象的行为随状态优雅变化
状态模式通过封装对象的状态,使行为随状态变化而改变。以订单为例,将待支付、已支付等状态独立成类,消除冗长条件判断,提升代码可维护性与扩展性,适用于状态多、转换复杂的场景。
327 0
|
2月前
|
设计模式 Java Spring
Java 设计模式之责任链模式:优雅处理请求的艺术
责任链模式通过构建处理者链,使请求沿链传递直至被处理,实现发送者与接收者的解耦。适用于审批流程、日志处理等多级处理场景,提升系统灵活性与可扩展性。
287 2
|
3月前
|
人工智能 Java API
Java与大模型集成实战:构建智能Java应用的新范式
随着大型语言模型(LLM)的API化,将其强大的自然语言处理能力集成到现有Java应用中已成为提升应用智能水平的关键路径。本文旨在为Java开发者提供一份实用的集成指南。我们将深入探讨如何使用Spring Boot 3框架,通过HTTP客户端与OpenAI GPT(或兼容API)进行高效、安全的交互。内容涵盖项目依赖配置、异步非阻塞的API调用、请求与响应的结构化处理、异常管理以及一些面向生产环境的最佳实践,并附带完整的代码示例,助您快速将AI能力融入Java生态。
542 12
|
4月前
|
设计模式 缓存 Java
Java设计模式(二):观察者模式与装饰器模式
本文深入讲解观察者模式与装饰器模式的核心概念及实现方式,涵盖从基础理论到实战应用的全面内容。观察者模式实现对象间松耦合通信,适用于事件通知机制;装饰器模式通过组合方式动态扩展对象功能,避免子类爆炸。文章通过Java示例展示两者在GUI、IO流、Web中间件等场景的应用,并提供常见陷阱与面试高频问题解析,助你写出灵活、可维护的代码。
|
4月前
|
设计模式 安全 Java
Java设计模式(一):单例模式与工厂模式
本文详解单例模式与工厂模式的核心实现及应用,涵盖饿汉式、懒汉式、双重检查锁、工厂方法、抽象工厂等设计模式,并结合数据库连接池与支付系统实战案例,助你掌握设计模式精髓,提升代码专业性与可维护性。
|
4月前
|
设计模式 XML 安全
Java枚举(Enum)与设计模式应用
Java枚举不仅是类型安全的常量,还具备面向对象能力,可添加属性与方法,实现接口。通过枚举能优雅实现单例、策略、状态等设计模式,具备线程安全、序列化安全等特性,是编写高效、安全代码的利器。
|
7月前
|
设计模式 缓存 安全
【高薪程序员必看】万字长文拆解Java并发编程!(8):设计模式-享元模式设计指南
🌟 ​大家好,我是摘星!​ 🌟今天为大家带来的是并发编程中的经典对象复用设计模式-享元模式,废话不多说让我们直接开始。
173 0
|
7月前
|
设计模式 Java 数据库连接
【设计模式】【结构型模式】代理模式(Proxy)
一、入门 什么是代理模式? 代理模式(Proxy Pattern)是一种结构型设计模式,允许你提供一个代理对象来控制对另一个对象的访问。 代理对象在客户端和目标对象之间起到中介作用,可以在不改变目标对
196 10

热门文章

最新文章