装饰器模式与JavaIO流

Posted 活跃的咸鱼

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了装饰器模式与JavaIO流相关的知识,希望对你有一定的参考价值。

装饰器模式

介绍:装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。

这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。

意图:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。

主要解决:一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。

何时使用:在不想增加很多子类的情况下扩展类。

如何解决:将具体功能职责划分,同时继承装饰者模式。

关键代码: 1、Component 类充当抽象角色,不应该具体实现。
2、修饰类引用和继承 Component 类,具体扩展类重写父类方法。

类图
在这里插入图片描述
类图中的角色说明

Component(抽象构件):它是具体构件和抽象装饰类的共同父类,声明了在具体构件中实现的业务方法,它的引入可以使客户端以一致的方式处理未被装饰的对象以及装饰之后的对象,实现客户端的透明操作。

ConcreteComponent(具体构件):它是抽象构件类的子类,用于定义具体的构件对象,实现了在抽象构件中声明的方法,装饰器可以给它增加额外的职责(方法)。

Decorator(抽象装饰类):它也是抽象构件类的子类,用于给具体构件增加职责,但是具体职责在其子类中实现。它维护一个指向抽象构件对象的引用,通过该引用可以调用装饰之前构件对象的方法,并通过其子类扩展该方法,以达到装饰的目的。

ConcreteDecorator(具体装饰类):它是抽象装饰类的子类,负责向构件添加新的职责。每一个具体装饰类都定义了一些新的行为,它可以调用在抽象装饰类中定义的方法,并可以增加新的方法用以扩充对象的行为。

由于具体构件类和装饰类都实现了相同的抽象构件接口,因此装饰模式以对客户透明的方式动态地给一个对象附加上更多的责任,换言之,客户端并不会觉得对象在装饰前和装饰后有什么不同。装饰模式可以在不需要创造更多子类的情况下,将对象的功能加以扩展。

装饰模式的核心在于抽象装饰类的设计。

JavaIO流中的典型案例

使用 Java I/O 的时候总是有各种输入流、输出流、字符流、字节流、过滤流、缓冲流等等各种各样的流,不熟悉里边的设计模式的话总会看得云里雾里的,现在通过设计模式的角度来看 Java I/O,会好理解很多。

jdk源码中的IO流中的InputStream就是典型的装饰器模式。
我们先来看看InputStream的体系结构
在这里插入图片描述

InputStream就是充当Component角色,FileInputStream,ObjectInputStream,等就是ConcreteComponent角色,FilterInputStream就是一个抽象装饰器,BufferedInputStream就是一个具体的装饰器。

InputStream的部分源码如下:
在这里插入图片描述
FilterInputStream的部分源码如下:
在这里插入图片描述
我们发现FilterInputStream中会聚合一个InputStream对象

下面看一下Java中包装流的实例:

import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.IOException;
public class StreamDemo {
    public static void main(String[] args) throws IOException{
        DataInputStream in=new DataInputStream
        (new BufferedInputStream(new  FileInputStream("D:\\\\hello.txt")));
        while(in.available()!=0) {
            System.out.print((char)in.readByte());
        }
        in.close();
    }
}
输出结果:
hello world!
hello Java I/O!

上面程序中对流进行了两次包装,先用 BufferedInputStream将FileInputStream包装成缓冲流也就是给FileInputStream增加缓冲功能,再DataInputStream进一步包装方便数据处理。

如果要实现一个自己的包装流,根据上面的类图,需要继承抽象装饰类 FilterInputStream

譬如来实现这样一个操作的装饰者类:将输入流中的所有小写字母变成大写字母。

import java.io.FileInputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;

public class UpperCaseInputStream extends FilterInputStream {
    protected UpperCaseInputStream(InputStream in) {
        super(in);
    }

    @Override
    public int read() throws IOException {
        int c = super.read();
        return (c == -1 ? c : Character.toUpperCase(c));
    }

    @Override
    public int read(byte[] b, int off, int len) throws IOException {
        int result = super.read(b, off, len);
        for (int i = off; i < off + result; i++) {
            b[i] = (byte) Character.toUpperCase((char) b[i]);
        }
        return result;
    }

    public static void main(String[] args) throws IOException {
        int c;
        InputStream in = new UpperCaseInputStream(new FileInputStream("D:\\\\hello.txt"));
        try {
            while ((c = in.read()) >= 0) {
                System.out.print((char) c);
            }
        } finally {
            in.close();
        }
    }
}
测试结果:
HELLO WORLD!
HELLO JAVA I/O!

这里总结几种常用流的应用场景:

流名称应用场景
ByteArrayInputStream访问数组,把内存中的一个缓冲区作为 InputStream 使用,CPU从缓存区读取数据比从存储介质的速率快10倍以上
StringBufferInputStream把一个 String 对象作为。InputStream。不建议使用,在转换字符的问题上有缺陷
FileInputStream访问文件,把一个文件作为 InputStream ,实现对文件的读取操作
PipedInputStream访问管道,主要在线程中使用,一个线程通过管道输出流发送数据,而另一个线程通过管道输入流读取数据,这样可实现两个线程间的通讯
SequenceInputStream把多个 InputStream 合并为一个 InputStream . “序列输入流”类允许应用程序把几个输入流连续地合并起来
DataInputStream特殊流,读各种基本类型数据,如byte、int、String的功能
ObjectInputStream对象流,读对象的功能
PushBackInputStream推回输入流,可以把读取进来的某些数据重新回退到输入流的缓冲区之中
BufferedInputStream缓冲流,增加了缓冲功能

整个Java IO体系都是基于字符流(InputStream/OutputStream) 和 字节流(Reader/Writer)作为基类,下面画出OutputStream、Reader、Writer类图

OutputStream类图
在这里插入图片描述

Writer类图
在这里插入图片描述
Reader类图
在这里插入图片描述

装饰者模式总结

装饰模式的主要优点如下:

  1. 对于扩展一个对象的功能,装饰模式比继承更加灵活性,不会导致类的个数急剧增加。
  2. 可以通过一种动态的方式来扩展一个对象的功能,通过配置文件可以在运行时选择不同的具体装饰类,从而实现不同的行为。
  3. 可以对一个对象进行多次装饰,通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合,得到功能更为强大的对象。
  4. 具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,原有类库代码无须改变,符合 “开闭原则”。

装饰模式的主要缺点如下:

  1. 使用装饰模式进行系统设计时将产生很多小对象,这些对象的区别在于它们之间相互连接的方式有所不同,而不是它们的类或者属性值有所不同,大量小对象的产生势必会占用更多的系统资源,在一定程序上影响程序的性能。
  2. 装饰模式提供了一种比继承更加灵活机动的解决方案,但同时也意味着比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为繁琐。

适用场景:

  1. 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
  2. 当不能采用继承的方式对系统进行扩展或者采用继承不利于系统扩展和维护时可以使用装饰模式。不能采用继承的情况主要有两类:第一类是系统中存在大量独立的扩展,为支持每一种扩展或者扩展之间的组合将产生大量的子类,使得子类数目呈爆炸性增长;第二类是因为类已定义为不能被继承(如Java语言中的final类)。

以上是关于装饰器模式与JavaIO流的主要内容,如果未能解决你的问题,请参考以下文章

Java IO—缓冲字符流以及IO中的装饰者模式

04.JavaIO流问题

装饰器模式和委托模式的区别

11 IO流——装饰器设计模式,Filter装饰流

装饰器模式及JAVA IO流例子★★★☆☆

5.19Java装饰器设计模式