用于IO的GoF Decorator模式的用例和示例

问题描述 投票:51回答:8

我在wikipedia中读到,Decorator模式用于.Net和Java IO类。

任何人都能解释一下这是如何使用的吗?它有一个可能的例子,有什么好处?

维基百科上有一个Windows窗体的例子,但我想知道Java IO类是如何发生的。

java .net design-patterns io decorator
8个回答
127
投票

InputStream是一个抽象类。像BufferedInputStreamGzipInputStreamObjectInputStream等大多数具体实现都有一个构造函数,它接受相同抽象类的实例。这是装饰器模式的识别键(这也适用于采用相同接口实例的构造函数)。

当使用这样的构造函数时,所有方法都将委托给包装的实例,并改变方法的行为方式。例如,事先在内存中缓冲流,预先解压缩流或以不同方式解释流。有些甚至还有其他方法最终也会进一步委托给包装的实例。这些方法用额外的行为装饰包装的实例。

假设我们在Gzip文件中有一堆序列化的Java对象,我们希望快速读取它们。

首先打开它的输入流:

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();
// ...

好处是您可以使用一个或多个装饰器来自由地装饰流以满足您的需求。这比为ObjectGzipBufferedFileInputStreamObjectBufferedFileInputStreamGzipBufferedFileInputStreamObjectGzipFileInputStreamObjectFileInputStreamGzipFileInputStreamBufferedFileInputStream等每种可能的组合提供单一课程要好得多。

请注意,当您即将关闭流时,只需关闭最外面的装饰器即可。它会将关闭呼叫一直委托给底部。

ois.close();

See also:


13
投票

让我们在浏览java IO类之前了解Decorator模式的组件。

enter image description here

Decorator模式有四个组成部分

  1. 组件:组件定义可以动态添加任务的对象的接口
  2. ConcreteComponent:它只是Component接口的一个实现
  3. Decorator:Decorator具有对Component的引用,并且还符合Component接口。装饰器本质上是包装组件
  4. ConcreteDecorator:ConcreteDecorator只是将责任添加到原始Component。

只要在设计时完成一些基础工作,装饰器模式可用于静态地(或在某些情况下)在运行时扩展(装饰)某个对象的功能,而与同一类的其他实例无关。这是通过设计一个包装原始类的新Decorator类来实现的。

现在让我们将这些概念映射到java.io pacakge类。

零件:

InputStream

此抽象类是表示输入字节流的所有类的超类。

需要定义InputStream子类的应用程序必须始终提供返回输入的下一个字节的方法。

public abstract int read()是一种抽象方法。

ConcreteComponent:

FileInputStream

FileInputStream从文件系统中的文件获取输入字节。可用的文件取决于主机环境。

FileInputStream用于读取原始字节流,例如图像数据。要读取字符流,请考虑使用FileReader。

InputStream的所有ConcreteComponents的示例:

AudioInputStream, ByteArrayInputStream, FileInputStream, FilterInputStream, 
InputStream, ObjectInputStream, PipedInputStream, SequenceInputStream, 
StringBufferInputStream

装饰:

FilterInputStream

FilterInputStream包含一些其他输入流,它将其用作其基本数据源,可能会沿途转换数据或提供其他功能。

请注意FilterInputStream实现InputStream => Decorator实现Component,如UML图所示。

public class FilterInputStream
extends InputStream

ConcreteDecorator:

BufferedInputStream

BufferedInputStream将功能添加到另一个输入流 - 即缓冲输入并支持标记和重置方法的功能。

所有ConcreteDecorators的示例:

BufferedInputStream, CheckedInputStream, CipherInputStream, DataInputStream, 
DeflaterInputStream, DigestInputStream, InflaterInputStream, 
LineNumberInputStream, ProgressMonitorInputStream, PushbackInputStream

工作示例代码:

我用BufferedInputStream读取一个单词的每个字符,它已存储在一个文本文件a.txt中

BufferedInputStream bis = new BufferedInputStream(new FileInputStream(new File("a.txt")));
while(bis.available()>0)
{
        char c = (char)bis.read();
        System.out.println("Char: "+c);;
}

何时使用此模式:

  1. 应动态添加/删除对象职责和行为
  2. 具体实施应与责任和行为分离
  3. 当子类化成本太高而无法动态添加/删除职责时

8
投票

在.NET中,有许多流装饰器,如BufferedStream,CryptoStream,GzipStream等。所有这些都装饰了Stream类。


4
投票

当您操作输入/输出流时,装饰器模式在java.io类中使用(同样适用于读者和编写者)。

inputstream,bytearrayinputstream,stringbuilderinputstreams等基于元素。 Filterinputstream是装饰器类的基类。过滤输入流(例如缓冲输入流)在读取流或写入流时可以执行其他操作。

它们是通过封装流来构建的,并且是流本身。

new BufferedReader( new FileInputStream() ).readLine();

我想不出在java.net中实现这种模式的任何类,但我认为你被告知这个包,因为它与java.io(例如socket.getInputStream)紧密相连。

实际上,here is a course from O'Relly解释了如何在java.io中实现装饰器。

此致,Stéphane


4
投票

A - Decorator Pattern

A.1 - Use Case of Decorator Pattern

装饰器模式用于在不更改遗留类的情况下扩展遗留功能。比方说,我们有一个实现接口的具体类。我们需要扩展现有方法的功能,因为现有的类及其方法已经被其他类使用,因此我们不希望在现有类中进行更改。但是我们还需要在新类上扩展功能,那么我们如何解决这个问题呢?

1- We can't change the existing legacy code
2- We want to extend the functionality

所以我们使用装饰器模式,将装饰器中的现有类包装起来。

B - 基本GoF装饰器模式示例

这里我们有一个简单的接口和一个实现/具体类。界面有一个简单的方法,即getMessageOfTheDay,它返回一个String。假设有很多其他类使用此方法。因此,如果我们想要在实现/具体类中进行更改,它将影响旧的遗留代码。我们只想为新类更改它,因此我们使用装饰器模式。

这是Gang Of Four装饰设计模式的一个简单例子;

B.1 - Greeter.java

public interface Greeter {
    String getMessageOfTheDay();
}

B.2 - BasicGreeter.java

public class BasicGreeter implements Greeter {

    @Override
    public String getMessageOfTheDay() {
        return "Welcome to my server";
    }

}

B.3 - Abstract Decorator Class: GreeterDecorator.java

public abstract class GreeterDecorator implements Greeter {

    protected Greeter greeter;

    public GreeterDecorator(Greeter greeter) {
        this.greeter = greeter;
    }

    public String getMessageOfTheDay() {
        return greeter.getMessageOfTheDay();
    }

}

B.4 - Concrete Decorator Class: StrangerDecorator.java

public class StrangerDecorator extends GreeterDecorator {

    public StrangerDecorator(Greeter greeter) {
        super(greeter);
    }

    @Override
    public String getMessageOfTheDay() {
        return "Hello Stranger " + super.getMessageOfTheDay();
    }

}

B.5 - Demo Code: DecoratorDemo .java

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);
    }

}

看看这些例子。需要抽象装饰器类来包装原始合同和实现。使用抽象装饰器,你可以创建更新的多个装饰器,但是在这个例子中,BasicGreeter被包装在抽象装饰器中,我们只在新的装饰器类上创建了StrangeGreeter。请通知装饰器类可以像火车一样使用,我们可以将装饰器包装在另一个装饰器内或相同。该功能是可扩展的,但原始类保留不做任何修改。

C - OutputStream演示

我们来看看这个例子。我们想用OutputStream写一个字符串到文件。这是演示代码;

C.1 - Sample OutputStream Demo To Write A File

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();
    }

}

C.2 - JSON Decorator Output: normal.txt

将在项目文件夹下创建一个名为“normal.txt”的新文件,内容将是;

I love Commodore 64

D - JSON OutputStream装饰器演示

现在,我想创建一个JSON包装器格式,如下所示;

{
    data: <data here>
}

我想要的是在简单的一个字段JSON格式内写入内容。我们怎样才能实现这一目标?有许多琐碎的方式。但是,我将通过编写扩展Java的OutputStream类的JSONDecorator来使用GoF装饰器模式;

D.1 - JSON Decorator for OutputStream: JSONStream.java

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());
    }

}

D.2 - JSON Decorator Demo: JSONDecoratorDemo.java

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();
    }

}

D.3 - JSON Decorator Output: json.txt

{
    data:"I love Commodore 64"
}

实际上,OutputStream本身就是一个Decorator Pattern,它是抽象装饰器,这里的具体装饰器是JSONStream类。


2
投票

可以装饰输入/输出流的一种方法是对其应用压缩/解压缩。例如,请参阅java.util.zip中的类。这样的装饰流可以与“常规”输入/输出流完全相同的方式使用,压缩/解压缩完全透明地执行。


2
投票

装饰器模式用于向现有对象添加功能,例如库中定义的类。然后,您可以“装饰”它以满足您的需求。如果您有兴趣了解更多关于模式的信息,我推荐Gang of Four的“Design Patterns”。


2
投票

好吧,我可能会迟到,但这个问题永远不会变老。理解Decorator的关键在于它使您能够将对象插入现有对象到另一个现有对象,依此类推。在构造函数中实现此模式很受欢迎。例如,

    Icecream ic = new RainbowTopUp(new ChocoTopUp(new Vanilla()));

如果你查看维基百科中的图表,你会看到ConcreteComponent和Decorator继承自相同的超类/接口Component。也就是说,这两个类具有相同的实现方法。

但是,在Decorator类中,您会看到一个返回Component的箭头,这意味着您可以在Decorator类中的某处使用Component。在这种情况下,您使用Component作为Decorator中构造函数的数据类型。这是一个重要的伎俩。如果没有这个技巧,您将无法将新对象插入现有对象。

之后,您可以创建从Decorator类继承的子类。因为所有类都具有相同的根,所以每个类都可以自由插入而无需任何顺序。

© www.soinside.com 2019 - 2024. All rights reserved.