您的位置:首页 > 编程语言 > Java开发

设计模式学习笔记--设计模式在Java I/O中的应用(装饰模式和适配器模式)

2014-04-06 17:43 911 查看

写在模式学习之前

什么是设计模式:在我们进行程序设计时,逐渐形成了一些典型问题和问题的解决方案,这就是软件模式;每一个模式描述了一个在我们程序设计中经常发生的问题,以及该问题的解决方案;当我们碰到模式所描述的问题,就可以直接用相应的解决方法去解决这个问题,这就是设计模式。

设计模式就是抽象出来的东西,它不是学出来的,是用出来的;或许你根本不知道任何模式,不考虑任何模式,却写着最优秀的代码,即使以“模式专家”的角度来看,都是最佳的设计,不得不说是“最佳的模式实践”,这是因为你积累了很多的实践经验,知道“在什么场合代码应该怎么写”,这本身就是设计模式。

有人说:“水平没到,学也白学,水平到了,无师自通”。诚然,模式背熟,依然可能写不出好代码,更别说设计出好框架;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);
		}
	}
}


结构模式(Structural Pattern)小结

结构模式(Structural Pattern)一共有七种,分别是:适配器模式、装饰模式、合成模式、代理模式、享元模式、门面模式、桥梁模式。

大致总结如下:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: