Detailed explanation of decorator mode (relationship with IO)

Posted by HhAaZzEeYy on Fri, 04 Feb 2022 04:36:55 +0100

https://www.cnblogs.com/zuoxiaolong/p/pattern11.html

This chapter discusses a design pattern, decorator pattern, which has a puzzled relationship with IO in JAVA.

Definition: Decoration mode is to dynamically extend the function of an object without changing the original class file and using inheritance. It wraps the real object by creating a wrapper object, that is, decoration.

This explanation is quoted from Baidu Encyclopedia. We pay attention to several points.

1. Do not change the original documents.

2. Do not use inheritance.

3. Dynamic expansion.

The above three sentences describe the characteristics of the decorator mode. The following LZ gives the class diagram of the decorator mode, which will be explained later.

As can be seen from the figure, what we decorate is any implementation class of an interface, and these implementation classes also include the decorator itself, which can also be decorated again.

In addition, this class diagram is only the complete structure of the decorator pattern, but there are many places that can be changed. LZ gives the following two.

It is strongly recommended that Component 1 be an ordinary interface and not even an abstract parent class.

2. The abstract parent class Decorator of Decorator is not required.

Then we will interpret the above standard decorator mode with our familiar JAVA code. The first is the interface Component to be decorated.

package com.decorator;

public interface Component {

    void method();
    
}

The next step is our specific interface implementation class, which is commonly known as the original object, or the object to be decorated.

package com.decorator;

public class ConcreteComponent implements Component{

    public void method() {
        System.out.println("Original method");
    }

}

The following is our abstract decorator parent class, which mainly defines what we need to decorate for the decorator and decorates the Component.

package com.decorator;

public abstract class Decorator implements Component{

    protected Component component;

    public Decorator(Component component) {
        super();
        this.component = component;
    }

    public void method() {
        component.method();
    }
    
}

Then comes our specific decorator A and decorator B.

package com.decorator;

public class ConcreteDecoratorA extends Decorator{

    public ConcreteDecoratorA(Component component) {
        super(component);
    }
    
    public void methodA(){
        System.out.println("Decorative device A Extended functions");
    }

    public void method(){
        System.out.println("Add a layer for this method A packing");
        super.method();
        System.out.println("A End of packaging");
    }
}

package com.decorator;

public class ConcreteDecoratorB extends Decorator{

    public ConcreteDecoratorB(Component component) {
        super(component);
    }
    
    public void methodB(){
        System.out.println("Decorative device B Extended functions");
    }

    public void method(){
        System.out.println("Add a layer for this method B packing");
        super.method();
        System.out.println("B End of packaging");
    }
}

Our test class is given below. We pack for many situations.

package com.decorator;

public class Main {

    public static void main(String[] args) {
        Component component =new ConcreteComponent();//Original object
        System.out.println("------------------------------");
        component.method();//Original method
        ConcreteDecoratorA concreteDecoratorA = new ConcreteDecoratorA(component);//Decorated as A
        System.out.println("------------------------------");
        concreteDecoratorA.method();//Original method
        concreteDecoratorA.methodA();//New method after decorating as A
        ConcreteDecoratorB concreteDecoratorB = new ConcreteDecoratorB(component);//Decorated as B
        System.out.println("------------------------------");
        concreteDecoratorB.method();//Original method
        concreteDecoratorB.methodB();//New method after decorating as B
        concreteDecoratorB = new ConcreteDecoratorB(concreteDecoratorA);//Decorate as A and then decorate as B
        System.out.println("------------------------------");
        concreteDecoratorB.method();//Original method
        concreteDecoratorB.methodB();//New method after decorating as B
    }
}

Let's take a look at the results of our operation and what effect it has produced.

From here, we can see that we first use the methods of the original class, and then call them after decorating A and B respectively. Finally, we use the two decorators together and call the methods defined by the interface.

Among the above, we have decorated the original method and added new functions to the decoration class respectively. methodA and methodB are the newly added functions, which can be done by the decorator. Of course, they don't necessarily have both, but generally there will be at least one, otherwise the significance of decoration will be lost.

In addition, at the beginning of the article, we talked about the relationship between IO and decorator. I believe you have generally heard that JAVA IO is implemented in decorator mode, so LZ will no longer talk nonsense. After giving a standard template example, we will directly take out the example of IO, and we will do it with real ammunition.

The following LZ directly gives some decoration processes in the IO package. The above LZ adds detailed notes and function demonstration of each decorator. You can compare it with the above standard decorator mode. LZ has to sigh that IO has a bad relationship with the decorator.

package com.decorator;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.PushbackInputStream;
import java.io.PushbackReader;

public class IOTest {

    /* test.txt Content:
     * hello world!
     */
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //The file path can be changed by itself
        final String filePath = "E:/myeclipse project/POITest/src/com/decorator/test.txt";
        
        //InputStream is equivalent to the decorated interface or abstract class. FileInputStream is equivalent to the original object to be decorated. FileInputStream cannot decorate InputStream
        //In addition, FileInputStream opens a file in read-only mode, and the handle of a file is stored in the handle attribute of the FileDescriptor object
        //Therefore, the following operations related to fallback and relabeling are the illusion caused by the establishment of a buffer in the heap, not the fallback or relabeling of a real file stream
        InputStream inputStream = new FileInputStream(filePath);
        final int len = inputStream.available();//Record the length of the stream
        System.out.println("FileInputStream I won't support it mark and reset: " + inputStream.markSupported());
        
        System.out.println("---------------------------------------------------------------------------------");
        
        /* The following shows the functions of the three decorators: bufferedinputstream, datainputstream, pushbackinputstream and LZ. The following shows the functions of the three decorators  */
        
        //First, it is decorated as BufferedInputStream, which provides the function of mark and reset
        BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);//Decorated as BufferedInputStream
        System.out.println("BufferedInputStream support mark and reset: " + bufferedInputStream.markSupported());
        bufferedInputStream.mark(0);//Mark it
        char c = (char) bufferedInputStream.read();
        System.out.println("LZ First character of the file:" + c);
        bufferedInputStream.reset();//Reset
        c = (char) bufferedInputStream.read();//Reread
        System.out.println("Read another character after reset, and it will still be the first character:" + c);
        bufferedInputStream.reset();
        
        System.out.println("---------------------------------------------------------------------------------");
        
        //Decorated as DataInputStream. In order to use DataInputStream and BufferedInputStream's mark reset function, we need another layer of packaging
        //Note that if the BufferedInputStream is not used here, but the original InputStream is used, the result returned by the read method will be - 1, that is, the reading has ended
        //Because BufferedInputStream has finished reading the contents of the text and buffered it on the heap, the default initial buffer size is 8192B
        DataInputStream dataInputStream = new DataInputStream(bufferedInputStream);
        dataInputStream.reset();//This is a function provided by BufferedInputStream. If it is not packaged on this basis, an error will occur
        System.out.println("DataInputStream Now have readInt,readChar,readUTF Other functions");
        int value = dataInputStream.readInt();//Read an int containing four bytes
        //We convert it into characters and display it in turn. You can see the first four characters of LZ file
        String binary = Integer.toBinaryString(value);
        int first = binary.length() % 8;
        System.out.print("use readInt First four characters read:");
        for (int i = 0; i < 4; i++) {
            if (i == 0) {
                System.out.print(((char)Integer.valueOf(binary.substring(0, first), 2).intValue()));
            }else {
                System.out.print(((char)Integer.valueOf(binary.substring(( i - 1 ) * 8 + first, i * 8 + first), 2).intValue()));
            }
        }
        System.out.println();
        
        System.out.println("---------------------------------------------------------------------------------");
        
        //PushbackInputStream cannot wrap BufferedInputStream to support mark reset because it overrides the reset and mark methods
        //Because the stream has been read to the end, we must reopen the handle of a file, FileInputStream
        inputStream = new FileInputStream(filePath);
        PushbackInputStream pushbackInputStream = new PushbackInputStream(inputStream,len);//Decorated as PushbackInputStream
        System.out.println("PushbackInputStream Return operation is supported after decoration unread");
        byte[] bytes = new byte[len];
        pushbackInputStream.read(bytes);//Read the whole stream
        System.out.println("unread Content before fallback:" + new String(bytes));
        pushbackInputStream.unread(bytes);//Go back
        bytes = new byte[len];//Empty byte array
        pushbackInputStream.read(bytes);//Reread
        System.out.println("unread Content after fallback:" + new String(bytes));
        
        System.out.println("---------------------------------------------------------------------------------");
        
        /*  There are two one-layer decoration and one two-layer decoration above. Next, we decorate it as Reader, and then carry out other decoration   */
        
        //Since the stream was previously read to the end by PushbackInputStream, we need to reopen the file handle again
        inputStream = new FileInputStream(filePath);
        InputStreamReader inputStreamReader = new InputStreamReader(inputStream,"utf-8");//Decorate it as InputStreamReader first
        System.out.println("InputStreamReader have reader Functions, such as transcoding:" + inputStreamReader.getEncoding());
        
        System.out.println("---------------------------------------------------------------------------------");
        
        BufferedReader bufferedReader = new BufferedReader(inputStreamReader);//We further decorate it as BufferedReader on the basis of reader
        System.out.println("BufferedReader have readLine Other functions:" + bufferedReader.readLine());
        
        System.out.println("---------------------------------------------------------------------------------");
        
        LineNumberReader lineNumberReader = new LineNumberReader(inputStreamReader);//We further decorate it as LineNumberReader on the basis of reader
        System.out.println("LineNumberReader It has the functions of setting the line number and obtaining the line number (the line number starts from 0),Current line number:" + lineNumberReader.getLineNumber());
        
        System.out.println("---------------------------------------------------------------------------------");
        
        //Here, because the readLine method has just read the stream to the end, we reopen the file handle again and need to wrap the inputstream into a reader again
        inputStreamReader = new InputStreamReader(new FileInputStream(filePath));
        PushbackReader pushbackReader = new PushbackReader(inputStreamReader,len);//We further decorate it as PushbackReader on the basis of reader
        System.out.println("PushbackReader It has a return operation reader object");
        char[] chars = new char[len];
        pushbackReader.read(chars);
        System.out.println("unread Content before fallback:" + new String(chars));
        pushbackReader.unread(chars);//Go back
        chars = new char[len];//Empty char array
        pushbackReader.read(chars);//Reread
        System.out.println("unread Content after fallback:" + new String(chars));
    }
}

The above is the use of IO decorator. InputStream is equivalent to the above Component interface, but here is an abstract class, which is the target abstract class of our decoration. FileInputstream is a concrete Component, that is, the specific object to be decorated. It is not a decorator in the IO structure of JAVA, because it cannot decorate InputStream. BufferedInputStream, DataInputstream and so on are all kinds of decorators. Compared with the above standard decorator templates, there are also abstract decorator base classes in JAVA IO, but the above is not reflected. It is FilterInputStream, which is the most basic decorator base class of many decorators.

In the above process, the dataInputStream is obtained after being decorated twice. It has the dual functions of dataInputStream and bufferedInputStream. In addition, the InputStreamReader is a special decorator, which provides a bridge from byte stream to character stream. In fact, in addition to the characteristics of decorator, it is also a bit like an adapter, But LZ still thinks it should be regarded as a decorator.

Other IO decorators can be tried by yourself or compared with the above standard decorator mode code. The results after the operation of LZ IO decorator program are attached below.

From the above display, we can fully understand the flexibility of decorator mode. We create a FileInputstream object. We can use various decorators to make it have different special functions, which is the best embodiment of the function of dynamically extending a class, and the flexibility of decorator mode is exactly what IO in JAVA needs, I have to praise that the builder of JAVA class library is really strong.

All classes of xxxyinputstream above inherit InputStream. This is not only to reuse the parent class function of InputStream (InputStream is also a template method mode, which defines the simple algorithm of read(byte []) method and gives the read() method to the specific InputStream for Implementation), but also to overlap decoration, that is, the decorator can also be decorated again, After the transition to Reader, the decorator system of Reader is similar.

The following LZ gives the class diagram of the classes involved in the above IO package. You can compare it with the above standard decorator mode.

LZ marked the roles of each class on the class diagram, and used the background color to separate the InputStream and Reader system. The left half is the decoration system of InputStream, and the right half is the decoration system of Reader. The bridge between them is InputStreamReader. Each decoration system is very similar to the above standard decorator pattern class diagram, You can have a look and feel for yourself, especially InputStreamReader, which has a special location.

In short, decorator pattern is a design pattern that can dynamically extend class functions very flexibly. It uses combination instead of inheritance to make the expansion of various functions more independent and flexible.

Topics: Java Design Pattern