Application scenario
Decorator pattern mainly solves the problem of too complex inheritance relationship, and replaces inheritance through composition. Its main function is to add enhancements to the original class.
One feature of decorator pattern is that multiple decorators can be nested on the original class. In order to meet this application scenario, when designing, the decorator class needs to inherit the same abstract class or interface as the original class.
Talk about Java IO classes
Java IO class library is very large and complex. There are dozens of classes, which are responsible for reading and writing IO data. We can divide IO into four categories.
Byte stream | Character stream | |
---|---|---|
Input stream | InputStream | Reader |
Output stream | OutputStream | Writer |
For different read and write scenarios, Java IO extends many subclasses based on these four parent classes.
I believe that when most developers first contact Java IO classes, they are confused by the huge Java IO class family: Why are there so many classes? What's the difference in their use?
Here to read config Taking the properties file as an example, InputStream is an abstract class, and FileInputStream is a subclass dedicated to reading file streams. BufferedInputStream is a data reading class with caching function, which can improve the efficiency of data reading.
InputStream in = new FileInputStream("config.properties"); InputStream bin = new BufferedInputStream(in); byte[] data = new byte[128]; while (bin.read(data) != -1) { //... }
Why doesn't Java IO design a BufferedFileInputStream class that inherits FileInputStream and supports caching? In this way, we can directly create new BufferedFileInputStream(), which is much simpler.
Inheritance issues
In fact, it's acceptable to just extend this class, but InputStream has many subclasses. If we continue to implement it in the way of inheritance, we need to continue to derive DataFileInputStream, DataPipedInputStream and other classes. If we also need classes that support both caching and reading data according to basic types, we need to continue to derive BufferedDataFileInputStream, BufferedDataPipedInputStream and other n classes. This only adds two enhancements. If we need to add more enhancements, it will lead to composition explosion, class inheritance structure becomes extremely complex, and the code is neither easy to expand nor easy to maintain.
Therefore, Java designers do not use inheritance to expand, but use combination. Here also reflects the design principle that combination is better than inheritance. The following is the simplified JDK source code
public abstract class InputStream { // ... } public class BufferedInputStream extends InputStream { protected volatile InputStream in; public BufferedInputStream(InputStream in) { this.in = in; } // Implement cache based read } public class DataInputStream extends InputStream { protected volatile InputStream in; public BufferedInputStream(InputStream in) { this.in = in; } // Implement reading basic data types }
If you check the source code of JDK, you will find that BufferedInputStream and DataInputStream do not inherit from InputStream, but another class called FilterInputStream.
This is because InputStream is an abstract class, and many of its methods already have default implementations. We want to reuse these implementations, but we implement them by injecting InputStream into the constructor, assigning it to the member variable InputStream in, and delegating all functions to the member variable in. Therefore, even if the method of the abstract class InputStream is inherited, the called object is the current class instance, not delegated to in, Therefore, you need to have a decorator class FilterInputStream.
public class FilterInputStream extends InputStream { protected volatile InputStream in; protected FilterInputStream(InputStream in) { this.in = in; } public int read() throws IOException { return in.read(); } public int read(byte b[]) throws IOException { return read(b, 0, b.length); } public int read(byte b[], int off, int len) throws IOException { return in.read(b, off, len); } public long skip(long n) throws IOException { return in.skip(n); } public int available() throws IOException { return in.available(); } public void close() throws IOException { in.close(); } public synchronized void mark(int readlimit) { in.mark(readlimit); } public synchronized void reset() throws IOException { in.reset(); } public boolean markSupported() { return in.markSupported(); } }
Is the decorator pattern simply "replacing inheritance with composition"?
Of course not. From the design of Java IO, the decorator mode has two special places:
- The decorator class inherits the same parent class as the original class, so that we can "nest" multiple decorator classes on the original class.
InputStream in = new FileInputStream("content.txt"); InputStream bin = new BufferedInputStream(in); DataInputStream din = new DataInputStream(bin); int data = din.readInt();
- Decorator class is an enhancement of functions, which is also an important feature of the application scenario of decorator mode.
Difference from agent mode
In proxy mode, the proxy class attaches functions unrelated to the original class, while in decorator mode, the decorator class attaches enhancements related to the original class.
Class diagram
code implementation
Shape
public abstract class Shape { public abstract void draw(); public abstract void doOther(); }
Circle
public class Circle extends Shape { @Override public void draw() { System.out.println("You are drawing circle..."); } @Override public void doOther() { System.out.println("You are doing other..."); } }
Square
public class Square extends Shape { @Override public void draw() { System.out.println("You are drawing square..."); } @Override public void doOther() { System.out.println("You are doing other..."); } }
ShapeDecorator
public class ShapeDecorator extends Shape { protected Shape shape; public ShapeDecorator(Shape shape) { this.shape = shape; } @Override public void draw() { shape.draw(); } @Override public void doOther() { shape.doOther(); } }
RedShapeDecorator
public class RedShapeDecorator extends ShapeDecorator { public RedShapeDecorator(Shape shape) { super(shape); } @Override public void draw() { shape.draw(); System.out.println("The shape color is red"); } }
Main
public class Main { public static void main(String[] args) { Shape shape; RedShapeDecorator decorator; shape = new Circle(); decorator = new RedShapeDecorator(shape); decorator.draw(); decorator.doOther(); shape = new Square(); decorator = new RedShapeDecorator(shape); decorator.draw(); decorator.doOther(); } }