Understand decorator patterns from InputStream

sequence

Russian nesting dolls. I think you all know them. If the expressions in each set are different, then for example, if I want to see the smiling face now, I just need the big set and the small set all the way to the smiling face. I think the classic implementation of the decorator pattern is like the Russian nesting dolls, where each doll is independent of the other, but can be used in combination. Think about it. Doesn’t it look like. The various InputStream streams in the JDK are classic implementations of the decorator pattern.

The body of the

1. Daily use of InputStream

In daily work, it is necessary to read files, and I will analyze InputStream from the daily code of reading files.

Case #1- Do not use a bufferInputStream
public static void main(String[] args) {
    try (FileInputStream fileInputStream = new FileInputStream("/Users/weiyunpeng/Documents/test.txt")) {
        StringBuilder content = new StringBuilder();
        final byte[] bytes = new byte[10];
        int offset;
        while((offset = fileInputStream.read(bytes)) ! = -1) {
            content.append(new String(bytes, 0, offset));
        }
        System.out.println("Read the contents of the file :" + content);
    } catch(IOException e) { e.printStackTrace(); }} # case2- Use one with a bufferInputStream
public static void main(String[] args) {
    try (FileInputStream fileInputStream = new FileInputStream("/Users/Documents/test.txt");
         BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream)) {
        StringBuilder content = new StringBuilder();
        final byte[] bytes = new byte[10];
        int offset;
        while((offset = bufferedInputStream.read(bytes)) ! = -1) {
            content.append(new String(bytes, 0, offset));
        }
        System.out.println("Read the contents of the file :" + content);
    } catch(IOException e) { e.printStackTrace(); }}Copy the code

The above code simply reads the test.txt file and prints its contents to the console. There is little difference between case 1 and case 2, and the output is the same. Case 2 uses FileInputStream and BufferedInputStream, and FileInputStream is passed as an argument to the constructor of BufferedInputStream. This allows you to use a feature unique to BufferedInputStream, the byte buffer. Let me draw a picture to illustrate the difference.

This is a hierarchy diagram without using a BufferedInputStream stream

This is the hierarchy diagram read after using the BufferedInputStream stream

2. Think – why does the JDK add different “features” to different streams by composing them

Suppose we had this requirement to add some special functionality to a class, how would we design it? Normally, we might inherit this class and override the methods in the parent class to add special functionality. Demands that is possible, but when the need is A variety of special features, the child will be more and more, will be more and more, it is difficult to maintain, this way will exist complex index level of inheritance, and various special function will not be able to free flexible combination between (A special function cannot at the same time there is B special features, unless again inheritance overriding methods), very troublesome.

Therefore, this problem can be avoided by composition, which is how the CLASSES related to input and output streams are implemented in the JDK. The principle is simple. There is a parent class (interface) InputStream, MyInputStream (read one byte at a time) and MyDecoratorInputStream (read a byte array as a buffer), each with different read stream properties. Let’s do it in code. The following abstract superclass, the read, write, foo method, has a basic implementation.

// Abstract superclass
public abstract class InputStream {
    public void read(a){
        System.out.println("InputStream default implementation");
    }

    public void write(a){
        System.out.println("InputStream default implementation");
    }

    public void foo(a){
        System.out.println("InputStream default implementation"); }}Copy the code

There are two subclasses, MyInputStream and MyDecoratorInputStream. MyDecoratorInputStream enhances the original read, write methods. When you need to use a reinforced method, you simply inject an instance of MyInputStream into an instance of MyDecoratorInputStream through the constructor, and then call the method of MyDecoratorInputStream. You can implement enhanced methods on top of the base functionality, the results of which are posted below.

// Subclass overrides all methods in the parent class
public class MyInputStream extends InputStream {
    @Override
    public void read(a){
        System.out.println("MyInputStream by default");
    }

    @Override
    public void write(a){
        System.out.println("MyInputStream by default");
    }

    @Override
    public void foo(a){
        System.out.println("MyInputStream by default"); }}// Decorator class -- Inherits the InputStream abstract class. Foo is not overridden
public class MyDecoratorInputStream extends InputStream{
    private InputStream inputStream;

    public MyDecoratorInputStream(InputStream inputStream){
        this.inputStream = inputStream;
    }

    @Override
    public void read(a){
        / / enhancement
        System.out.println("MyDecoratorInputStream read");
        // Original call
        inputStream.read();
    }

    @Override
    public void write(a){
        inputStream.write();
    }

    I won't override the foo method
}
/ / call
public class TestCode {
    public static void main(String[] args) {
        MyInputStream myInputStream = new MyInputStream();
        MyDecoratorInputStream myDecoratorInputStream = newMyDecoratorInputStream(myInputStream); myDecoratorInputStream.read(); myDecoratorInputStream.write(); myDecoratorInputStream.foo(); }}Copy the code

Running results:

Finally, if my enhancement class only needs to be enhanced for one method in the parent class, then the other methods in the parent class need to be implemented again. Suppose MyInputStream only needs to be enhanced for the read method in InputStream. But MyInputStream still needs to implement the write method, even though the method body simply calls super.write(); So how does the JDK solve this problem? If you look at the code below, BufferedInputStream implements the FilterInputStream class, not InputStream.

public class BufferedInputStream extends FilterInputStream {... }Copy the code

If you look at the FilterInputStream class, it provides some basic functionality for streams, so if there are other special features, the enhanced class simply inherits FilterInputStream and overwrites the methods that need to be enhanced. This eliminates the need to worry about unenhanced methods.

conclusion

Although the combination method is flexible to assemble various special functions, the multi-layer decoration also introduces complexity. List the pros and cons of use scenarios and decorator patterns.

Usage Scenarios:

  • Extend the functionality of a class.
  • Dynamic add function, dynamic undo.

Advantages: The decorator class and the decorator class can develop independently without coupling with each other. The decorator pattern is an inherited replacement pattern, and the decorator pattern can dynamically extend the functionality of an implementation class. Disadvantages: multi-layer decoration is more complex.

Seek wave concern, public number: Java Jinjintianlu