catalogue
why do you need decoration mode
how to implement decorator mode
Classic case of open source framework
HttpServletRequestWrapper in Java Servlet
InputStream, Reader, OutputStream and Writer families of Java
Comparison of advantages and disadvantages
what is decoration mode
Gof definition: dynamically add some additional responsibilities to an object. In terms of adding functions, the Decorator pattern is more flexible than generating subclasses. Alias Wrapper.
HeadFirst definition: dynamically attach responsibilities to objects. To extend functionality, decorators provide a more flexible alternative than inheritance.
A very important point here is that it is more flexible than inheritance. According to different translations, decoration mode is also called "painter mode".
why do you need decoration mode
First, let's introduce the common ways to add behavior to a class or object:
- Inheritance, using inheritance mechanism is an effective way to add functions to existing classes. By inheriting an existing class, a subclass can not only have its own methods, but also the methods of its parent class. However, this method is static, and users cannot control the way and timing of increasing behavior.
- Association, that is, object A of one class is embedded in another object B, and another object B determines whether to call the behavior of embedded object A in order to expand its own behavior.
Sometimes we want to add functions to an object rather than a class. For example, a graphical user interface toolbox allows you to add features to any user interface, such as window sliding. Using inheritance is a method, but this method is not flexible enough. Because the selection of borders is static, users cannot control the time and method of adding borders to components.
A more flexible way is to embed the component into another object and add a border from this object. We call it decoration. This decoration is as like as two peas, which are decorated with components, so it is transparent to customers who use this component. When a request is forwarded to this component, you can perform some additional actions before forwarding, such as drawing a border. The use of transparency can recursively nest multiple decorations to add any number of functions, as shown in the following figure.
how to implement decorator mode
Decorator mode mainly includes the following roles.
- Abstract Component role: define an abstract interface to standardize objects ready to receive additional responsibilities.
- Concrete component role: implement abstract components and add some responsibilities to them by decorating roles.
- Abstract Decorator role: inherits abstract components and contains instances of specific components. You can extend the functions of specific components through its subclasses.
- Concrete decorator role: implement relevant methods of abstract decoration and add additional responsibilities to specific component objects.
Classic case of open source framework
Compared with other design patterns, decorator pattern is a very common design pattern. Almost all open source frameworks and middleware will use this pattern. Here are some common examples
HttpServletRequestWrapper in Java Servlet
There is a class called HttpServletRequestWrapper in Java Servlet. This class wraps ServletRequest and can enhance the function. It can also be seen from the class description that the method mode is to call HttpServletRequest request request, but we can continue to wrap this class for function enhancement.
/** * Provides a convenient implementation of the HttpServletRequest interface * that can be subclassed by developers wishing to adapt the request to a * Servlet. * * <p>This class implements the Wrapper or Decorator pattern. Methods default * to calling through to the wrapped request object. * * @see javax.servlet.http.HttpServletRequest * @since Servlet 2.3 */ public class HttpServletRequestWrapper extends ServletRequestWrapper implements HttpServletRequest { /** * Constructs a request object wrapping the given request. * @throws java.lang.IllegalArgumentException if the request is null */ public HttpServletRequestWrapper(HttpServletRequest request) { super(request); } private HttpServletRequest _getHttpServletRequest() { return (HttpServletRequest) super.getRequest(); } /** * The default behavior of this method is to return getAuthType() * on the wrapped request object. */ @Override public String getAuthType() { return this._getHttpServletRequest().getAuthType(); } /** * The default behavior of this method is to return getCookies() * on the wrapped request object. */ @Override public Cookie[] getCookies() { return this._getHttpServletRequest().getCookies(); } /** * The default behavior of this method is to return getDateHeader(String name) * on the wrapped request object. */ @Override public long getDateHeader(String name) { return this._getHttpServletRequest().getDateHeader(name); }
InputStream, Reader, OutputStream and Writer families of Java
Let's take a look at the class diagram. InputStream and OutputStream are abstract components. All classes in the figure below can be wrapped, and some classes are not put here. In addition, for example, Writer and Reader have the same design idea, but one is byte operation and the other is character operation. But there will also be a problem, that is, class explosion.
The following is the main code of InputStream and FilterInputSteam. It can be seen from the code that FilterInputStream is a decorator. At the same time, BufferedInputStream, a subclass of FilterInputStream, is also a decorator. Like peeling onions, you will find that there are patterns layer by layer. People look for her thousands of times. When you look back, you are in the dim light.
/** * This abstract class is the superclass of all classes representing * an input stream of bytes. * * <p> Applications that need to define a subclass of <code>InputStream</code> * must always provide a method that returns the next byte of input. * * @author Arthur van Hoff * @see java.io.BufferedInputStream * @see java.io.ByteArrayInputStream * @see java.io.DataInputStream * @see java.io.FilterInputStream * @see java.io.InputStream#read() * @see java.io.OutputStream * @see java.io.PushbackInputStream * @since JDK1.0 */ public abstract class InputStream implements Closeable { // MAX_SKIP_BUFFER_SIZE is used to determine the maximum buffer size to // use when skipping. private static final int MAX_SKIP_BUFFER_SIZE = 2048; public abstract int read() throws IOException; public int read(byte b[]) throws IOException { return read(b, 0, b.length); } public int read(byte b[], int off, int len) throws IOException { if (b == null) { throw new NullPointerException(); } else if (off < 0 || len < 0 || len > b.length - off) { throw new IndexOutOfBoundsException(); } else if (len == 0) { return 0; } int c = read(); if (c == -1) { return -1; } b[off] = (byte)c; int i = 1; try { for (; i < len ; i++) { c = read(); if (c == -1) { break; } b[off + i] = (byte)c; } } catch (IOException ee) { } return i; } public long skip(long n) throws IOException { long remaining = n; int nr; if (n <= 0) { return 0; } int size = (int)Math.min(MAX_SKIP_BUFFER_SIZE, remaining); byte[] skipBuffer = new byte[size]; while (remaining > 0) { nr = read(skipBuffer, 0, (int)Math.min(size, remaining)); if (nr < 0) { break; } remaining -= nr; } return n - remaining; } public int available() throws IOException { return 0; } public void close() throws IOException {} public synchronized void mark(int readlimit) {} public synchronized void reset() throws IOException { throw new IOException("mark/reset not supported"); } public boolean markSupported() { return false; } }
public class FilterInputStream extends InputStream { /** * The input stream to be filtered. */ This is the wrapped input stream protected volatile InputStream in; Passing a wrapper class through a constructor protected FilterInputStream(InputStream in) { this.in = in; } The method of decoration class can be extended here public int read() throws IOException { return in.read(); }
Wrapper in Dubbo
For another example, sometimes in the R & D process, we will find that the environment is painful after all. At this time, we need to construct some mock classes to simulate the real call. At this time, the role of the decorator is reflected. For example, the core class Cluster in Dubbo disguises multiple invokers in the Directory as an Invoker, which is transparent to the upper layer and includes the fault-tolerant mechanism of the Cluster. The original Cluster has the following subclasses (omitted parts, such as FailFast, FailSafe and FailOver). We focus on the MockClusterWrapper class, which wraps the Cluster and returns a MockClusterInvoker to perform some mock operations.
public class MockClusterWrapper implements Cluster { private Cluster cluster; public MockClusterWrapper(Cluster cluster) { this.cluster = cluster; } @Override public <T> Invoker<T> join(Directory<T> directory) throws RpcException { return new MockClusterInvoker<T>(directory, this.cluster.join(directory)); } }
Others, such as the DataSourceWrapper in ShardingSphere, are for javax sql. Datasource is packaged. The writing method and idea here are similar, so we won't give examples one by one.
Usage scenario
- Add responsibilities to a single object in a dynamic and transparent manner without using other objects.
- Deal with those responsibilities that can be revoked.
- When the system cannot be extended by inheritance or inheritance is not conducive to system expansion and maintenance. There are two main types of situations where inheritance cannot be adopted: the first is that there are a large number of independent extensions in the system. In order to support each combination, a large number of subclasses will be generated, resulting in an explosive increase in the number of subclasses; The second type is because the class definition cannot inherit (such as final class)
Comparison of advantages and disadvantages
advantage
- It is more flexible than multiple inheritance. Compared with object inheritance, it adds a more flexible way to add responsibilities to objects. You can add and delete responsibilities at runtime by adding and separating methods and decorating. Using Decorator, you can easily add features repeatedly, such as Starbucks milk of double double.
- Avoid classes at the top of the hierarchy that have too many features. Instead of perceiving other customized classes, we can define a simple class and gradually add functions to it with the Decorator class.
shortcoming
- Will produce a lot of small objects, if overused, will make the program complex.
- This feature is more flexible than inheritance. Although it is easy for those who know these systems to customize them, it is difficult to learn these systems and troubleshoot them. For objects decorated for many times, finding errors during debugging may require level by level troubleshooting, which is cumbersome.
reference material
Gof design pattern: the basic collection of Reusable Object-Oriented Software