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

时间:2011-06-16 02:05:53

标签: java .net design-patterns io decorator

我已在wikipedia中读到装饰器模式用于 .Net Java IO 类。

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

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

8 个答案:

答案 0 :(得分: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();
// ...

好处是您可以使用一个或多个装饰器来自由地装饰流以满足您的需求。这比为ObjectGzipBufferedFileInputStreamObjectBufferedFileInputStreamGzipBufferedFileInputStreamObjectGzipFileInputStreamObjectFileInputStreamGzipFileInputStream,{BufferedFileInputStreamois.close(); ,{{1}}等所有可能的组合设置单个课程要好得多{1}}等等。

请注意,当您即将关闭流时,只需关闭最外层装饰器即可。它会将近距离通话委托给底部。

{{1}}

另见:

答案 1 :(得分:13)

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

enter image description here

Decorator模式有四个组件

  1. 组件: 组件定义可以动态添加责任的对象的接口
  2. ConcreteComponent:它只是 Component 接口的实现
  3. 装饰器: 装饰器具有对组件的引用,并且还符合组件接口。 Decorator实际上是包装组件
  4. ConcreteDecorator: ConcreteDecorator 只是将责任添加到原始组件
  5. 装饰器模式可以用于静态地(装饰)某个对象的功能,或者在某些情况下在运行时,独立于同一类的其他实例,只要在设计时完成一些基础工作。这是通过设计一个包装原始类的新 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. 当子分类成本太高而无法动态添加/删除职责时

答案 2 :(得分:8)

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

答案 3 :(得分: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中实现装饰器。

此致  斯特凡

答案 4 :(得分:4)

A - 装饰者图案

A.1 - 装饰器模式的使用案例

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

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 - 抽象装饰器类: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 - 具体装饰器类:StrangerDecorator.java

public class StrangerDecorator extends GreeterDecorator {

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

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

}

B.5 - 演示代码: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 - 示例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();
    }

}

C.2 - JSON装饰器输出:normal.txt

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

I love Commodore 64

D - JSON OutputStream装饰器演示

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

{
    data: <data here>
}

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

D.1 - OutputStream的JSON装饰器: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装饰器演示: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装饰器输出:json.txt

{
    data:"I love Commodore 64"
}

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

答案 5 :(得分:2)

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

答案 6 :(得分:2)

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

答案 7 :(得分:2)

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

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

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

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

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