我在 维基百科 上看到过装饰器模式被用于 .Net 和 Java IO 类。
有人能解释一下它是如何使用的吗?还有它的好处是什么,能否给出一个可能的例子?
维基百科上有关于 Windows表单 的示例,但我想知道它如何应用于 Java IO 类。
InputStream
是一个抽象类。大部分具体的实现类,如BufferedInputStream
、GzipInputStream
和ObjectInputStream
等,都有一个构造函数,该函数接受一个相同的抽象类实例作为参数。这是装饰器模式的标志性特征(该模式还适用于构造函数接收相同接口实例的情况)。
当使用这样的构造函数时,所有方法将委托给包装的实例,并以不同的方式改变方法的行为。例如,在预先缓存流、预先解压流或不同地解释流方面进行更改。一些实现甚至具有额外的方法,它们最终也会进一步委托给包装的实例。这些方法使用额外的行为修饰了包装的实例。
假设我们有一堆序列化的Java对象存储在一个Gzipped文件中,并且我们想要快速读取它们。
首先打开该文件的输入流:
FileInputStream fis = new FileInputStream("/objects.gz");
我们需要速度,因此让我们在内存中进行缓冲:
BufferedInputStream bis = new BufferedInputStream(fis);
这个文件是被gzip压缩过的,所以我们需要将其解压:
GzipInputStream gis = new GzipInputStream(bis);
我们需要反序列化这些 Java 对象:ObjectInputStream ois = new ObjectInputStream(gis);
现在我们终于可以使用它了:SomeObject someObject = (SomeObject) ois.readObject();
// ...
优点在于您可以使用一个或多个各种修饰器来装饰流以适应您的需求,这比拥有每种可能组合的单个类如ObjectGzipBufferedFileInputStream
、ObjectBufferedFileInputStream
、GzipBufferedFileInputStream
、ObjectGzipFileInputStream
、ObjectFileInputStream
、GzipFileInputStream
、BufferedFileInputStream
等更好。
请注意,在关闭流时,只需关闭最外层的修饰器即可。它会将关闭调用代理到底部。
ois.close();
FileInputStream
没有接受InputStream
作为参数的构造函数。 - senseiwu在讨论Java IO类之前,我们先了解一下装饰器(Decorator)模式的组成部分。
装饰器模式包含四个组成部分(来源于维基百科):
- 组件(Component):定义了可以动态添加职责的对象接口。
- 具体组件(ConcreteComponent):是Component接口的实现。
- 装饰器(Decorator):持有一个对Component的引用,并遵循Component接口。装饰器本质上是将Component进行了包装。
- 具体装饰器(ConcreteDecorator):只是为原始Component添加了职责。
现在,让我们将这些概念映射到java.io包中的类。
组件(Component):
这个抽象类是所有表示字节输入流的类的超类。AudioInputStream, ByteArrayInputStream, FileInputStream, FilterInputStream,
InputStream, ObjectInputStream, PipedInputStream, SequenceInputStream,
StringBufferInputStream
装饰器:
FilterInputStream 包含另一个输入流,它将其作为基本数据源,可能会在此过程中转换数据或提供其他功能。
请注意,FilterInputStream
实现了 InputStream
=> 装饰器实现了 UML 图中的组件。
public class FilterInputStream
extends InputStream
具体装饰者:
BufferedInputStream 添加了对另一个输入流的功能,即缓冲输入并支持标记和重置方法。
所有具体装饰者的示例:
BufferedInputStream, CheckedInputStream, CipherInputStream, DataInputStream,
DeflaterInputStream, DigestInputStream, InflaterInputStream,
LineNumberInputStream, ProgressMonitorInputStream, PushbackInputStream
工作示例代码:
我使用了BufferedInputStream
来读取存储在文本文件a.txt中的单词的每个字符。
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(new File("ravindra.txt")));
while(bis.available()>0)
{
char c = (char)bis.read();
System.out.println("Character = "+c);
}
何时使用此模式:
FileInputStream
怎么被认为是具体构件? 它不是一个装饰器,因为它需要一个 InputStream
参数吗? - nikhil装饰器模式用于在不修改旧类的情况下扩展旧功能。假设我们有一个实现接口的具体类。我们需要扩展现有方法的功能,但是因为现有类及其方法已被其他类使用,因此我们不想更改现有类。但是我们也需要在新类上获得扩展功能,那么我们该如何解决这个问题呢?
1- We can't change the existing legacy code
2- We want to extend the functionality
因此,我们使用装饰者模式,在装饰器内部包装现有的类。
这里有一个简单的接口和实现/具体类。该接口有一个简单的方法,即getMessageOfTheDay
,它返回一个String
。假设有许多其他类使用此方法。因此,如果我们想要更改实现/具体类,则会影响旧遗留代码。我们只想为新类更改它,因此我们使用装饰器模式。
这是一个Gang Of Four Decorator设计模式的微不足道的例子;
public interface Greeter {
String getMessageOfTheDay();
}
public class BasicGreeter implements Greeter {
@Override
public String getMessageOfTheDay() {
return "Welcome to my server";
}
}
public abstract class GreeterDecorator implements Greeter {
protected Greeter greeter;
public GreeterDecorator(Greeter greeter) {
this.greeter = greeter;
}
public String getMessageOfTheDay() {
return greeter.getMessageOfTheDay();
}
}
public class StrangerDecorator extends GreeterDecorator {
public StrangerDecorator(Greeter greeter) {
super(greeter);
}
@Override
public String getMessageOfTheDay() {
return "Hello Stranger " + super.getMessageOfTheDay();
}
}
public class DecoratorDemo {
public static void main(String[] args) {
Greeter greeter = new BasicGreeter();
String motd = greeter.getMessageOfTheDay();
System.out.println(motd);
Greeter newGreeter = new StrangerDecorator(greeter);
String newMotd = newGreeter.getMessageOfTheDay();
System.out.println(newMotd);
Greeter muchNewGreeter = new StrangerDecorator(new StrangerDecorator(greeter));
String newestMotd = muchNewGreeter.getMessageOfTheDay();
System.out.println(newestMotd);
}
}
让我们看一下这个例子。我们想用OutputStream将字符串写入文件。以下是演示代码;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class FileWriterDemo {
public static void main(String[] args) throws IOException {
File file = new File("./normal.txt");
file.createNewFile();
OutputStream oStream = new FileOutputStream(file);
String content = "I love Commodore 64";
oStream.write(content.getBytes());
oStream.close();
}
}
在项目文件夹下将创建一个名为"normal.txt"的新文件,其内容如下:
I love Commodore 64
现在,我想创建一个以下格式的 JSON 包装器;
{
data: <data here>
}
public class JSONStream extends OutputStream {
protected OutputStream outputStream;
public JSONStream(OutputStream outputStream) {
this.outputStream = outputStream;
}
@Override
public void write(int b) throws IOException {
outputStream.write(b);
}
@Override
public void write(byte[] b) throws IOException {
String content = new String(b);
content = "{\r\n\tdata:\"" + content + "\"\r\n}";
outputStream.write(content.getBytes());
}
}
public class JSONDecoratorDemo {
public static void main(String[] args) throws IOException {
File file = new File("./json.txt");
file.createNewFile();
OutputStream oStream = new FileOutputStream(file);
JSONStream js = new JSONStream(oStream);
String content = "I love Commodore 64";
js.write(content.getBytes());
js.close();
oStream.close();
}
}
{
data:"I love Commodore 64"
}
实际上,OutputStream 本身就是一个装饰器模式,而这里的抽象装饰器是它自己,具体装饰器是 JSONStream 类。
new BufferedReader( new FileInputStream() ).readLine();
您可以通过应用压缩/解压缩来装饰输入/输出流。例如,可以查看java.util.zip
中的类。这样装饰的流可以像“常规”的输入/输出流一样使用,完全透明地执行压缩/解压缩。
装饰者模式用于为已存在的对象(例如在库中定义的类)添加功能。您可以将其“装饰”以适应您的需求。如果您对学习模式感兴趣,我建议阅读《设计模式》(Gang of Four著)。
嗯,我可能有点晚了,但是这个问题从来没有过时。理解装饰器的关键是它使您能够将一个对象插入到另一个现有对象中,然后再插入到另一个现有对象中,依此类推。在构造函数中实现此模式很流行。例如:
Icecream ic = new RainbowTopUp(new ChocoTopUp(new Vanilla()));
然而,在Decorator类中,您会看到一个指向Component的箭头,这意味着您在Decorator类中使用了Component。在这种情况下,您将Component用作Decorator中构造函数的数据类型。这就是重点所在。如果没有这个技巧,您将无法将新对象插入到现有对象中。
之后,您可以创建从Decorator类继承的子类。由于所有类都具有相同的根,因此每个类都可以自由地插入而无需任何顺序。