Примеры использования и примеры шаблонов GoF Decorator для ввода-вывода

Я прочитал в википедии, что шаблон Decorator используется для классов .Net и Java IO.

Кто-нибудь может объяснить, как это используется? И в чем выгода от возможного примера?

В википедии есть пример форм Windows, но я хочу знать, как это происходит с классами Java IO.

8 ответов

Решение

InputStream это абстрактный класс. Большинство конкретных реализаций, таких как BufferedInputStream, GzipInputStream, ObjectInputStream и т. д. иметь конструктор, который принимает экземпляр того же абстрактного класса. Это ключ распознавания шаблона декоратора (это также относится к конструкторам, использующим экземпляр того же интерфейса).

Когда такой конструктор используется, все методы будут делегированы обернутому экземпляру с изменениями в поведении методов. Например, предварительно буферизуя поток в памяти, предварительно распаковывая поток или интерпретируя поток по-разному. У некоторых даже есть дополнительные методы, которые, наконец, также делегируют дальше обернутому экземпляру. Эти методы украшают упакованный экземпляр дополнительным поведением.

Допустим, у нас есть несколько сериализованных Java-объектов в файле Gzipped, и мы хотим быстро их прочитать.

Сначала откройте поток ввода этого:

FileInputStream fis = new FileInputStream("/objects.gz");

Нам нужна скорость, поэтому давайте запишем ее в память:

BufferedInputStream bis = new BufferedInputStream(fis);

Файл распакован, поэтому нам нужно распаковать его:

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

Смотрите также:

Давайте разберемся с компонентами шаблона Decorator, прежде чем изучать Java-классы ввода-вывода.

Узор декоратора состоит из четырех компонентов

  1. Компонент: Компонент определяет интерфейс для объектов, к которым можно динамически добавлять обязанности
  2. ConcreteComponent: это просто реализация интерфейса компонентов
  3. Декоратор: Декоратор имеет ссылку на Компонент, а также соответствует интерфейсу Компонента. Декоратор по существу оборачивает Компонент
  4. ConcreteDecorator: ConcreteDecorator просто добавляет обязанности к оригинальному компоненту.

Шаблон декоратора может использоваться для расширения (декорирования) функциональности определенного объекта статически или, в некоторых случаях, во время выполнения, независимо от других экземпляров того же класса, при условии, что во время разработки сделаны некоторые фундаментальные работы. Это достигается путем разработки нового класса Decorator, который оборачивает исходный класс.

Теперь давайте сопоставим эти понятия с классами java.io pacakge.

Составная часть:

InputStream:

Этот абстрактный класс является суперклассом всех классов, представляющих входной поток байтов.

Приложения, которым необходимо определить подкласс InputStream, всегда должны предоставлять метод, который возвращает следующий байт ввода.

public abstract int read() это абстрактный метод.

ConcreteComponent:

FileInputStream:

FileInputStream получает входные байты из файла в файловой системе. Какие файлы доступны, зависит от среды хоста.

FileInputStream предназначен для чтения потоков необработанных байтов, таких как данные изображения. Для чтения потоков символов рассмотрите использование FileReader.

Примеры всех Конкретных Компонентов InputStream:

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

Decorator:

FilterInputStream:

FilterInputStream содержит некоторый другой входной поток, который он использует в качестве основного источника данных, возможно преобразовывая данные по пути или предоставляя дополнительную функциональность.

Обратите внимание, что FilterInputStream инвентарь InputStream => Декоратор реализует Компонент, как показано на диаграмме UML.

public class FilterInputStream
extends InputStream

ConcreteDecorator:

BufferedInputStream

BufferedInputStream добавляет функциональность к другому входному потоку, а именно, возможность буферизовать ввод и поддерживать методы меток и сброса.

Примеры всех Бетонных Декораторов:

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. Когда подклассы слишком дороги, чтобы динамически добавлять / удалять обязанности

В.NET есть несколько потоковых декораторов, таких как BufferedStream, CryptoStream, GzipStream и т. Д. Все они украшают Stream учебный класс.

A - Образец Декоратора

A.1 - Вариант использования шаблона Decorator

Шаблон Decorator используется для расширения устаревшей функциональности без изменения унаследованного класса. Допустим, у нас есть конкретный класс, который реализует интерфейс. И нам нужно расширить функциональность существующего метода, потому что существующий класс и его методы уже используются другими классами, поэтому мы не хотим вносить изменения в существующие классы. Но нам также нужны расширенные функциональные возможности в новом классе, тогда как мы решаем эту проблему?

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

Поэтому мы используем шаблон декоратора, оборачивая существующий класс внутри декораторов.

B - Базовый пример шаблона GoF Decorator

Здесь у нас есть простой интерфейс и класс реализации / бетона. Интерфейс имеет один простой метод, который getMessageOfTheDay и он возвращает String, Предположим, что есть много других классов, использующих этот метод. Поэтому, если мы хотим внести изменения в реализацию / конкретный класс, это повлияет на старый унаследованный код. Мы хотим изменить его только для новых классов, поэтому используем шаблон декоратора.

Вот тривиальный пример шаблона Gang Of Four Decorator Design;

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 Demo

Давайте посмотрим на этот пример. Мы хотим записать строку в файл с помощью 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 с одним полем. Как мы можем достичь этой цели? Есть много тривиальных способов. Однако я буду использовать шаблон GoF Decorator, написав JSON Decorator, который расширяет класс OutputStream в Java;

D.1 - JSON-декоратор для 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: JSON DecoratorDemo.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.

Шаблон декоратора используется в классах java.io, когда вы манипулируете потоками ввода / вывода (то же самое относится и к читателям и писателям).

inputtream, bytearrayinputstream, stringbuilderinputstreams и т. д. являются основанными элементами. Filterinputstream является базовым классом для классов декораторов. Фильтровать входные потоки (такие как буферизованный входной поток) могут делать дополнительные вещи, когда они читают потоки или записывают в них.

Они создаются путем инкапсуляции потока и сами являются потоками.

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

Я не могу представить ни одного класса, реализующего этот шаблон в java.net, но я думаю, что вам сказали об этом пакете, так как он сильно привязан к java.io (например, socket.getInputStream).

Собственно, вот курс от О'Релли, который объясняет, как декоратор реализован в java.io.

С уважением, Стефан

Шаблон декоратора используется для добавления функциональности к существующим объектам, таким как класс, определенный в библиотеке. Затем вы можете "украсить" его под свои нужды. Если вы хотите узнать больше о шаблонах, я рекомендую "Дизайн шаблонов" от Gang of Four.

Ну, я могу опоздать на вечеринку, но этот вопрос никогда не устареет. Ключевым моментом для понимания Decorator является то, что он дает вам возможность подключить объект к существующему объекту к другому существующему объекту и так далее. Популярно применять этот шаблон в конструкторе. Например,

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

Если вы посмотрите на диаграмму в википедии, вы увидите, что ConcreteComponent и Decorator наследуются от одного и того же суперкласса / интерфейса, Component. То есть эти два класса имеют одинаковые методы реализации.

Однако в классе Decorator вы увидите стрелку назад к Компоненту, что означает, что вы используете Компонент где-нибудь в классе Decorator. В этом случае вы используете Компонент как тип данных конструктора в Декораторе. Это большая хитрость. Без этого трюка вы не сможете подключить новый объект к существующему объекту.

После этого вы можете создавать подклассы, наследуемые от класса Decorator. Поскольку все классы имеют одинаковый корень, каждый отдельный класс может свободно подключаться без какого-либо порядка.

Один из способов украсить поток ввода / вывода - применить к нему сжатие / декомпрессию. Смотрите занятия в java.util.zip, например. Такой декорированный поток может использоваться точно так же, как "обычный" поток ввода / вывода, причем сжатие / декомпрессия выполняется полностью прозрачно.

Другие вопросы по тегам