设计模式学习笔记--设计模式在Java I/O中的应用(装饰模式和适配器模式)
写在模式学习之前
什么是设计模式:在我们进行程序设计时,逐渐形成了一些典型问题和问题的解决方案,这就是软件模式;每一个模式描述了一个在我们程序设计中经常发生的问题,以及该问题的解决方案;当我们碰到模式所描述的问题,就可以直接用相应的解决方法去解决这个问题,这就是设计模式。
设计模式就是抽象出来的东西,它不是学出来的,是用出来的;或许你根本不知道任何模式,不考虑任何模式,却写着最优秀的代码,即使以“模式专家”的角度来看,都是最佳的设计,不得不说是“最佳的模式实践”,这是因为你积累了很多的实践经验,知道“在什么场合代码应该怎么写”,这本身就是设计模式。
有人说:“水平没到,学也白学,水平到了,无师自通”。诚然,模式背熟,依然可能写不出好代码,更别说设计出好框架;OOP理解及实践经验到达一定水平,同时也意味着总结了很多好的设计经验,但"无师自通",却也未必尽然,或者可以说,恰恰是在水平和经验的基础上,到了该系统的学习一下“模式”的时候了,学习一下专家总结的结果,印证一下自己的不足,对于提高水平还是很有帮助的。
本系列的设计模式学习笔记,实际是对于《Java与模式》这本书的学习记录。
Java I/O库的设计原则
在Java 语言I/O库的设计中,使用了两个结构模式,即装饰模式和适配器模式。本篇围绕这两个模式讨论Java I/O库的设计。
Java库的两个对称性
(1)输出-输入对称:处理Byte流的InputStream和OutputStream;处理Char流的Reader和Writer。
(2)byte-char对称:InputStream与Reader的子类分别负责Byte和Char的输入;OutputStream与Writer的子类分别负责Byte和Char流的输出,它们分别形成平行的等级结构。
Java库的两个设计模式
(1)装饰模式:装饰模式在Java语言中的最著名的应用莫过于Java I/O标准库的设计了。
(2)适配器模式:适配器模式是Java I/O库中第二个最重要的设计模式。
装饰模式的应用
InputStream类型中的装饰模式
结构图:
装饰模式的各个角色:
(1)抽象构件(Component)角色:由InputStream扮演。这是一个抽象类,为各种子类型流处理器提供统一的接口。
(2)具体构件(ConcreteComponent)角色:由ByteArrayInputStream、FileInputStream、PipedInputStream以及StringBufferInputStream等原始流处理器扮演。他们实现了抽象构件角色所规定的接口,可以被链接流处理器所装饰。
(3)抽象装饰(Decorator)角色:由FilterInputStream扮演。它实现了InputStream所规定的接口。
(4)具体装饰(ConcreteDecorator)角色:由几个类扮演,分别是DataInputStream、BufferInputStream以及两个不常用的类LineNumberInputStream和PushBackInputStream
注意:StringBufferInputStream、LineNumberInputStream已经过时,不再推荐使用。
OutputStream类型中的装饰模式
结构图:
装饰模式的各个角色:
(1)抽象构件(Component)角色:由OutputStream扮演。这是一个抽象类,为各种的子类型流处理器提供统一的接口。
(2)具体构件(ConcreteComponent)角色:由ByteArrayOutputStream、FileOutputStream以及PipedOutputStream等扮演,它们均实现了OutputStream所声明的接口。
(3)抽象装饰(Decorator)角色:由FilterOutputStream扮演。它有与OutputStream相同的接口,而这正是装饰类的关键。
(4)具体装饰(ConcreteDecorator)角色:由几个类扮演,分别是BufferedOutputStream、DataOutputStream,以及PrintStream。
Reader类型中的装饰模式
结构图:
装饰模式的各个角色:
(1)抽象构件(Component)角色: 由Reader扮演。这是一个抽象类,为各种的子类型流处理器提供统一的接口。
(2)具体构件(ConcreteComponent)角色:有CharArrayReader、InputStreamReader、PipedReader以及StringReader等扮演,它们均实现了Reader所声明的接口。
(3)抽象装饰(Decorator)角色:由BufferedReader以及FilterReader扮演。这两者有着与Readeer相同的接口,而这正是装饰类的关键。
(4)具体装饰(ConcreteD)角色:分别是LineNumberReader作为BufferedReader的具体装饰角色,PushbackReader作为FilterReader的具体装潢角色。
Writer类型中的装饰模式
结构图:
装饰模式的各个角色:
(1)抽象构件(Component)角色:由Writer扮演。这是一个抽象类,为各种的子类型流处理器提供统一的接口。
(2)具体构件(ConcreteComponent)角色:由CharArrayWriter、OutputStreamWriter、PipedWriter以及StringWriter等扮演,它们均实现了Reader所声明的接口。
(3)抽象装饰(Decorator)角色:由BufferedWriter、FilterWriter以及PrintWriter扮演,它们有着与Writer相同的接口。
(4)具体装饰(ConcreteDecorator)角色:是与抽象装饰角色合并的。由于抽象装饰角色与具体装饰角色发生合并,因为装饰模式在这里被简化了。
装饰模式和适配器模式的对比
(1)装饰模式和适配器模式,都是通过封装其他对象达到设计目的的。
(2)理想的装饰模式在对被装饰对象进行功能增强时,要求具体构件角色、装饰角色的接口与抽象构件角色的接口完全一致;而适配器模式则不然,一般而言,适配器模式并不要求对源对象的功能进行增强,只是利用源对象的功能而已,但是会改变源对象的接口,以便和目标接口相符合。
(3)装饰模式有透明和半透明两种,区别就在于接口是否完全一致。关于装饰模式的重要的事实是,很难找到理想的装饰模式。一般而言,对一个对象进行功能增强的同时,都会导致加入新的行为,因此,装饰角色的接口比抽象构件角色的接口宽是很难避免的,这种现象存在于Java I/O库中多有的类型的链接流处理器中。一个装饰类提供的新的方法越多,它离纯装饰模式的距离就越远,离适配器模式的距离也就越近。
适配器模式的应用
InputStream原始流处理器中的适配器模式
ByteArrayInputStream是一个适配器类:
FileInputStream是一个适配器类:
StringBufferInputStream是一个适配器类:
OutputStream原始流处理器中的适配器模式
ByteArrayOutputStream是一个适配器类:
FileOutputStream是一个适配器类:
PipedOutputStream是一个适配器类:
Reader原始流处理器中的适配器模式
CharArrayReader是一个适配器类:
StringReader是一个适配器类:
其他,关于InputStreamReader,PipedReader等也都是适配器类。
Writer类型中的适配器模式
CharArrayWriter是一个适配器类:
PipedWriter是一个适配器类:
StringWriter是一个适配器类:
Java I/O 代码示例
缓冲输入文件
如果想要打开一个文件用于字符输入,可以使用FileReader。为了提供速度,我们可能希望对那个文件进行缓冲,那么我们将所产生的引用传递给一个BufferedReader对象。
代码示例(BufferedInputFile.java)如下:
//缓冲输入文件 import java.io.*; class BufferedInputFile { public static String read(String filename) throws IOException { //Reading input by lines BufferedReader in = new BufferedReader(new FileReader(filename)); String s; StringBuilder sb = new StringBuilder(); //注意这里使用的StringBuilder是JDK5.0引入的,它和StringBuffer的唯一区别是它不是线程安全的,因而性能更高 while((s = in.readLine()) != null) { sb.append(s + "\n"); } in.close(); return sb.toString(); } public static void main(String[] args) throws IOException { System.out.println(read("BufferedInputFile.java")); } }
从内存输入内容
使用StringReader读取字符,或者使用DataInputStream读取字节。代码如下://从内存输入:使用StringReader,无中文乱码问题 import java.io.*; class MemoryInput { public static void main(String[] args) throws IOException { StringReader in = new StringReader(BufferedInputFile.read("MemoryInput.java")); int c; while((c = in.read()) != -1) { System.out.print((char)c); } } } //从内存输入;使用DataInputStream,这是一个面向字节的I/O类(不是面向字符的),有中文乱码问题 class FormattedMemoryInput { public static void main(String[] args) throws IOException { try { DataInputStream in = new DataInputStream(new ByteArrayInputStream(BufferedInputFile.read("MemoryInput.java").getBytes())); while(true) { System.out.print((char)in.readByte()); } } catch (EOFException e) { System.err.println("End Of Stream"); } } } //从内存输入;判断文件结尾 class TestEOF { public static void main(String[] args) throws IOException { DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream("MemoryInput.java"))); while(in.available() != 0) { System.out.print((char)in.readByte()); } } }
基本的文件输出
FileWriter对象可以向文件写入数据。通常会 用BufferedWriter将其包装起来 用以缓冲输出(缓冲往往能显著地增加I/O操作的性能)。在本例中,为了提供格式化机制,它被装饰成PrintWriter。安装这种方式创建的数据可作为普通文本读取。代码如下:
//基本的文件输出 import java.io.*; class BasicFileOutput { public static void main(String[] args) throws IOException { BufferedReader in = new BufferedReader(new StringReader(BufferedInputFile.read("BasicFileOutput.java"))); //PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter("BasicFileOutput.out"))); //也可以使用简化的方式,依旧是在进程缓存,而不必自己去实现。遗憾的是,其他常见的写入任务都没有快捷方式 //因此,典型的I/O人就包含大量的冗余文本 PrintWriter out = new PrintWriter("BasicFileOutput.out"); int lineCount = 1; String s; while((s = in.readLine()) != null) { out.println(lineCount++ + ": " + s); } out.close(); //Show the stored file: System.out.println(BufferedInputFile.read("BasicFileOutput.out")); } }
存储和恢复数据
PrintWriter可以对数据进行格式化,以便人们的阅读。如果为了输出可供另一个”流“恢复的数据,则需要用DataOutputStream写入数据,并用DataInputStream恢复数据。当然,这些流可以是任何形式,但在下面的示例中使用的是一个文件,并且对于读写都进行了缓冲处理。代码如下:
//存储和恢复数据 import java.io.*; class StoringAndRecoveringData { public static void main(String[] args) throws IOException { DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("Data.txt"))); out.writeDouble(3.14159); out.writeUTF("你好"); out.writeDouble(1.111222); out.writeUTF("‘That‘s True"); out.close(); DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream("Data.txt"))); System.out.println(in.readDouble()); System.out.println(in.readUTF()); System.out.println(in.readDouble()); System.out.println(in.readUTF()); } }
用GZIP进行简单压缩
GZIP接口非常简单,如果我们只想对单个数据流(而不是一系列互异数据)进行压缩,它可能比较适合。代码如下:
//用GZIP进行简单压缩 import java.io.*; import java.util.zip.*; class GZIPcompress { public static void main(String[] args) throws IOException { if(args.length == 0) { System.out.println("Usage:\nGZIPcompress file\nUses GZIP compression to compress the file to test.gz"); System.exit(1); } //写压缩文件入磁盘 BufferedReader in = new BufferedReader(new FileReader(args[0])); BufferedOutputStream out = new BufferedOutputStream( new GZIPOutputStream(new FileOutputStream("test.gz"))); System.out.println("Writing file"); int c; while((c = in.read()) != -1) { out.write(c); } in.close(); out.close(); //从磁盘读取压缩文件 System.out.println("Reading file"); BufferedReader in2 = new BufferedReader( new InputStreamReader(new GZIPInputStream(new FileInputStream("test.gz")))); String s; while(( s = in2.readLine()) != null) { System.out.println(s); } } }
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。