[转]设计模式在Java I/O中的应用(装饰模式和适配器模式)

在Java语言的IO库的设计中,使用了两个结构模式,即装饰模式和适配器模式,本章就围绕这两个模式讨论Java的IO库的设计. 

引言 

Java语言采用流的机制来实现输入/输出,所谓流,就是数据的有序排列,它可以从某个源(流源)出来,到某个目的地(流汇)。根据流的方向可以将流分为输出流和输入流.例如Java程序可以用FileInputStream类从一个磁盘文件读入byte类型数据,也可以用FileOutputStream类向磁盘文件写入byte类型的数据. 
在实际应用中,程序需要写出的数据往往是非常结构化的信息,因此这些byte类型的数据实际上是一些数值,文字,源代码等。 Java的IO库提供了一个称为链接(Chaining)的机制,可以将一个流管道与另一个流管道首尾相接,以其中之一的输出为输入,形成一个流管道的链接.如DataInputStream流管道可以把FileInputStream流管道的输出当做输入,将byte类型的数据转换成Java的原始类型和String类型的数据.类似地,向一个文件写入byte类型的数据不是一个简单的过程。一个程序需要向一个文件里面写入的数据往往是结构化的,因此定入disk文件时首先要经过转换,DataOutputStream流管道提供了接收原始数据类型和String类型的方法,这个流管道会将得到的数据转换成byte类型的数据并输出来。所以,我们可以将DataOutputStream与FileOutputStream链接起来,我们向DataOutputStream里写入原始类型的数据或String类型的数据,FileOutputStream得到从DataOutputStream输出的byte类型的数据并输出到disk文件中. 
流管道所处理的流必定都有流源,流源可以分为两大类: 
(A)数组,String,File等,这一种叫做原始流源 
(B)同样类型的流用做链接流类的流源,叫做链接流源. 

IO库的设计原则 

Java的IO库是对各种常见的流源,流汇以及处理过程的抽象化。客户端的Java程序不必知道最终的流源,流汇是磁盘上的文件还是一个数组,或者是一个线程,也不必插手到诸如数据是否经过缓冲,可否按照行号读取等.要理解Java的IO,关键要掌握两个对称和两个设计模式. 
(1)Java IO的两个对称 
(A)输出-输入对称:比如InputStream和OutputStream各自占据byte流的输入与输出的两个平行的等级结构的根部,而Reader和Writer各自占据char流的输入与输出的两个平行的根部. 
(B)byte-char对称:InputStream与Reader的子类分别负责byte和char流的输入,outputStream与writer的子类分别负责byte和char流的输出.它们分别形成平行的等级结构. 
(2)Java IO库的两个设计模式 
(1)装饰模式 
(2)适配器模式 

装饰模式的应用 

装饰模式在Java语言中的最著名应用莫过于Java IO标准库的设计了。本节就以byte流的处理为例,讲解装饰模式是怎么样应用到Java IO的设计中去的. 
由于Java的IO需要很多性能的各种组合,如果这些性能都是用继承的方法实现的,那么每种组合都需要一个类,这样就会造成大量性能重复的类出现,装饰模式可以尽可能地解决这些问题。在使用IO时,必须理解Java的IO是由一些基本的原始流处理器和围绕它们的装饰流处理器所组成的,这样可以在学习和使用IO时做到事半功倍的效果. 

(1)InputStream类型中的装饰模式 

InputStream有七个直接的具体子类,有四个属于FilterInputStream的具体子类: 
InputStream 
     | 
ByteArrayInputStream 
FileInputStream 
ObjectInputStream 
PipedInputStream 
SequenceInputStream 
StringBufferInputStream 
FilterInputStream 
   | 
BufferedInputStream 
DataInputStream 
LineNumberInputStream 

PushbackInputStream 

\

以上的流类根据输入流的源的类型,可以将这些流类分成两种:原始流类和链接流处理器. 
(A)原始流处理器 
原始流处理器接收一个byte数组对象,String对象,FileDescriptor对象等,在InputStream类型的流处理器中,原始流处理器包括有如下四种: 
*ByteArrayInputStream:为多线程通信提供缓冲操作功能,接收一个byte数组作为流源 
*FileInputStream:建立一个与文件有关的输入流,接收一个File对象作为流的源. 
*PipedInputStream:可以与PipedOutputStream配合使用,用于读入一个数据管道的数据,接收一个PipedOutputStream作为源. 
*StringBufferInputStream:将一个字符串缓冲区转换为一个输入流,接收一个String对象作为流的源。 
(B)链接流处理器 
所谓链接流处理器,就是可以接收另一个(同种类)的流对象作为流源,并对之进行功能扩展的类。 InputStream类型的链接流处理包括: 
*FilterInputStream:称为过滤输入流,它将一个输入流作为流源,它有如下四个子类:BufferInputStream(用来从硬盘将数据读入到一个内存缓冲区中,并从此缓冲区提供数据),DataInputStream(提供基于多字节的读取方法,可以读取原始数据类型的数据),LineNumberInputStream(提供带有行计数功能的过滤输入流),PushpackInputStream(提供特殊功能,可以将已经读取的字节"推回"到输入流中) 
*ObjectInputStream:可以将ObjectOutputStream串行化的原始数据类型和对象重新并行化 
*SeqcueneInputStream可以将两个已有的输入流连接起来,形成一个输入流,从而将多个输入流排列构成一个输入流序列。 
值得指出的是,虽然PipedInputStream接收一个流对象PipedOutputStream作为流的源,但是,PipedOutputStream流对象的类型不是InputStream,因此PipedInputStream流处理器仍然属于原始流处理器. 

(2)装饰模式的各个角色 

在所有的InputStream类型的链接流处理器中,使用频率最大的就是FilterInputStream类,以这个类为抽象装饰角色的装饰模式结构非常明显和典型,下面就以这个类为核心说明装饰模式的各个角色是由哪些流处理器扮演的: 
(A)抽象构件(Component)角色:由InputStream扮演,这是一个抽象类,为各种子类型流处理器提供统一的接口. 
(B)具体构件(ConcreteComponent)角色:由ByteArrayInputStream,FileInputStream,PipedInputStream以及StringBufferInputStream等原始流处理器扮演,它们实现了抽象构件角色所规定的接口,可以被链接流处理器所装饰. 
(C)抽象装饰(Decorator)角色:由FilterInputStream扮演,它实现了InputStream所规定的接口 
(D)具体装饰(ConcreteDdcorator)角色:由几个类扮演,分别是DataInputStream,BufferedInputStream以及两个不常用的LineNumberInputStream和PushbackInputStream. 
可以知道,所谓有链接流就是装饰角色,而原始流就是具体构件角色.一方面,链接流对象接收一个同类型的原始流对象或者另一个同类型的链接流对象作为流源,另一方面,它们都对流源对象的内部工作方法做了相应的改变,这种改变誻装饰模式所要达到的目的,比如: 
BufferedInputStream装饰了InputStream的内部工作方式,使得流的读入操作使用缓冲机制,在使用缓冲机制后,不会对每一次的流读入操作都产生一个物理读盘动作,从而提高了程序的效率. 

(3)OutputStream类型中的装饰模式 

OutputStream是一个用于输出的抽象类,它的接口,子类的等级结构,子类的功能都和InputStream有很好的对称性,在OutputStream给出的接口里,将write换成read就得到了InputStream接口. 
OutputStream 
  | 
ByteArrayOutputStream 
FileOutputStream 
ObjectOutputStream 
PipedOutputStream 
FilterOutputStream 
       | 
BufferedOutputStream 
DataOutputStream 
PrintStream 

\

在OutputStream类型的流处理器中,原始流处理器包括以下三种: 

*ByteArrayOutputStream:为多线程的通信提供缓冲区操作功能,输出流的汇集是一个byte数组 
*FileOutputStream:建立一个与文件有关的输出流,输出流是汇集是一个文件对象 
*PipedOutputStream:可以与PipedInputStream配合使用,用于向一个数据管道输出数据. 
OutputStream类型的链接流处理器包括以下几种: 
*BufferOutputStream:用来向一个内存缓冲区中写出数据,并将此缓冲区的数据输出到硬盘中. 
*DataOutputStream:提供基于多字节的写出方法,可以写出原始数据类型的数据 
*PrintStream:用于产生格式化输出,System.out就是一个PrintStream 
*ObjectOutputStream:可以将原始数据类型和对象串行化. 

(4)OutputStream类型中的装饰模式的各个角色 

在所有的链接流处理器中,最常见的就是FilterOutputStream类,现在看装饰模式中的角色分别被哪些类扮演: 
(A)抽象构件(Component)角色:由OutputStream扮演,这是一个抽象类,为各种子类型的流处理器提供统一接口 
(B)具体构件(ConcreteComponent)角色:由ByteArrayOutputStream,FileOutputStream以及PipedOutputStream等扮演,它们都实现了OutputStream所声明的接口 
(C)抽象装饰(Decorator)角色,由FilterOutputStream扮演,它有与OutputStream相同的接口,而这正是装饰类的关键 
(D)具体装饰(ConcreteDecorator)角色:由几个类扮演,分别是BufferedOutputStream,DataOutputStream以及PrintStream。 
所以再次看到,所谓的链接流,其实就是装饰模式中的装饰角色,而原始流就是具体构件角色. 
与DataInputStream相对应的是DataOutputStream,后者负责将由原始数据类型和String对象组成的数据格式化并输出到一个流中,使得任何机器上的任何DataInputStream类型的对象都可以读入这些数据,所有的方法都是以write开始的,如writeByte(),writeFloat(); 
如果需要对数据做到真正的格式化,以便输出到像控制台显示那样,就需要用到PrintStream,PrintStream可以对由原始数据类型和String对象组成的数据进行格式化,以形成可以阅读的格式,而DataOutputStream则不同,它将数据输出到一个流中,以便DataInputStream可以在任何机器和操作系统中都可以重新将数据读入. 
BufferedOutputStream对一个输出流进行装饰,使得写出操作使用缓冲机制,在使用了缓冲机制之后,不会对每一次的流写入操作都产生一个物理的写动作,从而提高了程序效率,在涉及到物理流如控制台I/O,文件IO,都应当使用这个装饰流处理器. 

(5)Reader类型中的装饰模式 

\

在Reader类型的流处理器中,原始流处理器有以下四种: 
*CharArrayReader:为多线程的通信提供缓冲区操作功能 
*InputStreamReader:这个类有一个子类FileReader 
*PipedReader:可以与PipedOutputStream配合使用,用于读入一个数据管道的数据 
*StringReader:建立一个与文件有关的输入流 
而链接流处理器包括以下几种: 
*BufferedReader:用来从硬盘将数据读入到一个内存缓冲区中,并从此缓冲区中提供数据,它的子类有LineNumberReader. 
*FilterReader:成为过滤输入流,它将另一个输入流作为流的来源,这个类的子类有PushbackReader,提供基于多字节的读取方法,可以读取原始数据类型的数据. 
现在看下装饰模式的各个角色由哪些类来扮演: 
(A)抽象构件(Component)角色:由Reader扮演,这是一个抽象类,为各种子类型处理器提供统一接口 
(B)具体构件(ConcreteComponent)角色:由CharArrayReader,InputStreamReader,PipeReader以及StringReader等扮演,它们均实现了Reader所声明的接口. 
(C)抽象装饰(Decorator)角色:由BufferReader以及FilterReader扮演,这两者有着与Reader相同的接口,而这正是装饰类的关键,它们分别给出两个装饰角色的等级结构,第一个给出了LineNumberReader作为具体装饰角色,另一个给出PushbackReader作为具体装饰角色. 
(D)具体装饰(ConcreteDecorator)角色:如(C)所述 

(6)Writer中的装饰模式 

Writer类型是一个与Reader类型平行的等级结构,而且Writer类型的等级结构几乎是与Reader类型的等级结构关于输入/输出是对称的. 
Writer 
   | 
BufferWriter 
CharArrayWriter 
FilterWriter 
PipedWriter 
PrintWriter 
StringWriter 
OutputStreamWriter 

FileWriter 

\

CharArrayWriter:为多线程的通信提供缓冲区的操作功能 

OutputStreamWriter:建立一个与文件有关的输出流,含有一个具体子类的FileWriter,为Writer类型的输出流提供文件输出功能 
PipedWriter:可以与PipedOutputStream配合使用,用于读入一个数据管道的数据 
StringWriter:向一个StringBuffer写出数据. 
而链接流处理器包括: 
BufferedWriter:为Writer类型的流处理提供缓冲区功能 
FilterWriter:称为过滤输入流,它将别一个输入流作为流的来源,这是一个没有子类的抽象类 
PrintWriter:支持格式化的文字输出. 

(7)Writer中装饰模式中的角色由哪些类扮演 
(A)抽象构件(Component)角色:由Writer扮演,这是一个抽象类,为各种子类型流处理器提供统一的接口 
(B)具体构件(ConcreteComponent)角色:由CharArrayWriter,OutputStreamWriter,PipedWriter以及StringWriter等扮演,它们均实现了Reader所声明的接口. 
(C)抽象装饰(Decorator)角色:由BufferedWriter,FilterWriter以及PrintWriter扮演,这两者有着与Writer相同的接口 
(D)具体装饰(ConcreteDecorator)角色:是与抽象装饰角色合并的. 
由于抽象装饰角色与具体装饰角色发生合并,因此装饰模式在这里被简化了。 

装饰模式和适配器模式的对比

(1)装饰模式和适配器模式,都是通过封装其他对象达到设计目的的。

(2)理想的装饰模式在对被装饰对象进行功能增强时,要求具体构件角色、装饰角色的接口与抽象构件角色的接口完全一致;而适配器模式则不然,一般而言,适配器模式并不要求对源对象的功能进行增强,只是利用源对象的功能而已,但是会改变源对象的接口,以便和目标接口相符合。

(3)装饰模式有透明和半透明两种,区别就在于接口是否完全一致。关于装饰模式的重要的事实是,很难找到理想的装饰模式。一般而言,对一个对象进行功能增强的同时,都会导致加入新的行为,因此,装饰角色的接口比抽象构件角色的接口宽是很难避免的,这种现象存在于Java I/O库中多有的类型的链接流处理器中。一个装饰类提供的新的方法越多,它离纯装饰模式的距离就越远,离适配器模式的距离也就越近。

适配器模式的应用 

(A)InputStream原始流处理器中适配器模式 
(A1)ByteArrayInputStream是一个适配器类 
它继承了InputStream的接口,而封装了一个byte数据,换言之,它将一个byte数组的接口适配成InputStream流处理器的接口.Java语言支持四种类型,Java接口,Java类,Java数组和原始类型.前三种是引用类型,类和数组的实例是对象,原始类型的值不是对象.换言之,Java语言的数组是像所有的其他对象一样,而不管数组中所存储的元素是什么类型.这样一来,ByteArrayInputStream就符合适配器模式的描述,是一个对象形式的适配器类,如下图: 

\

(A2)FileInputStream是一个适配器类 
FileInputStream继承了InputStream,同时持有一个对FileDescriptor的引用,这是一个将FileDescriptor对象适配成InputStream类型的对象形式的适配器模式,如下图: 
\
(A3)StringBufferInputStream是一个适配器类 
它继承了InputStream,同时持有一个对String对象的引用.这是一个将String对象适配成InputStream类型的对象形式的适配器模式. 
\
(B)OutputStream原始流处理器中的适配器模式 
由于IO设计的对称性,这里只补充PipedOutputStream适配器类: 
PipedOutputStream总是与PipedInputStream一同使用的,它接收一个类型为PipedInputStream的输入类型,并将之转换成类型为OutputStream类型的输出流,如图: 
\ \

(C)Reader原始流处理器中的适配器模式 
(C1)CharArrayReader是一个适配器类 
CharArrayReader将一个char数组适配成为Reader类型的输入流: 
(C2)StringReader是一个适配器类 
StringReader继承了Reader,持有一个对String对象的引用,它将String的接口适配成Reader类型的接口. 
(C3)InputStreamReader将在后面作专题讨论 
\ \
(D)Writer类型中的适配器模式 
由于IO设计的对称性,这里只补充PipedWriter: 
PipedWriter总是和PipedReader一同使用的,它将一个PipedReader对象的接口适配成一个Writer类型的接口: 
\ \ \

专题:从byte流到char流的适配 
(A)InputStreamReader 
在Java语言的标准库Java IO里面,有一个InputStreamReader类叫做桥梁(bridge)类,在文档里说:InputStreamReader是从byte流到char流的一个桥梁,它读入byte数据并根据指定的编码将之翻译成char数据.InputStreamReader虽然叫做"桥梁",但是它不是桥梁模式的应用,而是适配器模式,如图: 
Reader InputStream 
   | | 
    InputStreamReader 
当把InputStreamReader与任何InputStream的具体子类链接时,可以从InputStream的输出读入byte类型的数据,将之转换成char类型的数据: 
源(byte类型数据)--InputStream-->byte类型的数据-->InputStreamReader-->汇(char类型的数据) 
(B)OutputStreamWriter 
同样地,OutputStreamWriter是从byte输出流到char输出流的一个适配器,或者说OutputStreamWriter是从OutputStream到Writer的适配器,如: 
Writer OutputStream 
  | | 
   OutputStreamWriter 
换言之,当与任何一个OutputStream的具体子类相链接时,OutputStreamWriter可以将char类型的数据转换成byte类型的数据,再交给输出流: 
源(char类型的数据)-->OutputStreamWriter-->byte类型的数据-->OutputStream-->汇(byte类型的数据) 

一个例子说明InputStreamReader的使用: 
Java代码   收藏代码
  1. package cai.milenfan.basic.test;   
  2.   
  3. import java.io.BufferedReader;   
  4. import java.io.IOException;   
  5. import java.io.InputStreamReader;   
  6.   
  7. public class Echo {   
  8. public static void main(String[] args) throws IOException{   
  9. String line;   
  10. InputStreamReader input = new InputStreamReader(System.in);   
  11. System.out.println("Enter data and press enter:");   
  12. BufferedReader reader = new BufferedReader(input);   
  13. line = reader.readLine();   
  14. System.out.println("Data entered is :" line);   
  15. }   
  16. }   
可以看出,这个类接收一个类型为InputStream的System.in对象,将之适配成Reader类型,然后再使用BufferedReader类"装饰"Reader,将缓冲功能加上去,这样一来就可以使用BufferedReader对象的readLine()方法读入整行的数据,数据类型为String。得到这个数据后,程序又将它写对System.out中去,完成了全部的流操作.如下管道图: 
源(后台输入的byte类型的数据)-->System.in-->byte类型的数据-->InputStreamReader-->char类型的数据-->BufferedReader-->String类型的数据-->System .out-->byte类型的数据(汇:输出到后台) 
这里值得指出的是:本例用了BufferedReder来为流的读入提供缓冲功能,这样做的直接效果是可以使用readLine()方法按行读入数据,但是由于Reader接口并不提供readLine()方法,所以这样一来,例子中就必须申明一个BufferedReader类型的流处理器,而不是一个Reader类型的流处理器,这意味着装饰模式的退化. 
在上面的例子中,InputStreamReader起到了适配器的作用,它将一个byte类型的输入流适配成一个char类型的输入流,在这之后,BufferedReader则起到了装饰模式的作用,将缓冲机制引入到流的读入中。因此这个简单的例子涉及到两个设计模式. 

六:中文信息的读写 
Java语言的IO库提供了两套平行的独立的等级结构,即InputStream,OutputStream和Reader,Writer。前者处理8位元的流,后者处理16位元的流,由于Java语言的byte数据类型是8位元的,而char是16位元的,所以中文信息需要用第二个等级结构类才能读写. 
几乎所有的8位元的Java语言的IO库类都有相应的16位元的IO库类,但是有很多时候需要把两种库类结合起来用,这时候,Java语言所提供的桥梁类就变得很有用,例如InputStreamReader把InputStream适配到Reader,而OutputStreamWriter把OutputStream适配到Writer类,换言之,它们把byte流适配成了char流. 
例如,因为英文的DOS窗口的字集是ISO Latin-1,如果要从英文的DOS窗口读入中文的输入,就需要把System.in适配成Reader: 
InputStreamReader isr = new InputStreamReader(System.in); 
另一方面,如果要写到DOS窗口去,就需要把Writer适配成System.out 
OutputStreamWriter osw = new OutputStreamWriter(System.out); 
如果要读取一个在Macintosh写好的文件,需要这样: 
FileInputStream fis = new FileInputStream("symbol.txt"); 
InputStreamReader isr = new InputStreamReader(fis,"MacSymbol"); 
反过来,向这个文件里写则需要: 
FileOutputStream fos = new FileOutputStream("symbol.txt"); 
OutputStreamWriter osw = new OutputStreamWriter(fos,"MacSymbol");
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值