从开源框架理解设计模式系列#Decorator装饰模式

目录

what什么是装饰模式

why为什么需要装饰模式

how如何实现装饰者模式

开源框架经典案例

Java Servlet中的HttpServletRequestWrapper

Java的InputStream、Reader、OutputStream、Writer家族

Dubbo中的Wrapper

使用场景

优缺点对比

优点

缺点

参考资料


what什么是装饰模式

        Gof定义:动态地给一个对象添加一些额外的职责。就增加功能来说, Decorator模式相比生成子类更为灵活。别名Wrapper。
        HeadFirst定义:动态地将责任附加到对象上,若要扩展功能,装饰者提供了比继承更有弹性的替代方法。

        这里很重要的一点是是比继承更有弹性,根据翻译的不同,装饰模式也有人称之为“油漆工模式”。

why为什么需要装饰模式

        首先介绍一下常见的给一个类或者对象增加行爲的方式:

  • 继承,使用继承机制是给现有类添加功能的一种有效途径,通过继承一个现有类可以使得子类在拥有自身方法的同时还拥有父类的方法。但是这种方法是静态的,用户不能控制增加行为的方式和时机。
  • 关联,即将一个类的对象A嵌入另一个对B象中,由另一个对象B来决定是否调用嵌入对象A的行为以便扩展自己的行为。

        有时候我们希望给某个对象而不是添加功能,例如一个图形用户界面工具箱允许你对任意的用户界面添加特性,比如窗口滑动。使用继承是一种方法,但是这种方法不够灵活,因为边框的选择是静态的,用户不能控制对组件加边框的时间和方法。

        比较灵活的方式是将组件嵌入到另一个对象中,由这个对象添加边框。我们称之为装饰。这个装饰与它所装饰的组件接口一模一样,因此对使用该组件的客户透明。在请求转发给这个组件的时候,可以在转发前执行一些额外的动作,比如画一个边框,透明性的使用可以递归的嵌套多个装饰,从而添加任意多的功能,如下图。 

how如何实现装饰者模式

        装饰器模式主要包含以下角色。

  1. 抽象构件(Component)角色:定义一个抽象接口以规范准备接收附加责任的对象。
  2. 具体构件(ConcreteComponent)角色:实现抽象构件,通过装饰角色为其添加一些职责。
  3. 抽象装饰(Decorator)角色:继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
  4. 具体装饰(ConcreteDecorator)角色:实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。

装饰模式的结构图

开源框架经典案例

        相比其他设计模式,装饰者模式是十分普遍的设计模式,几乎各个开源框架,中间件都是会使用该模式,下面举几个常见的例子.

Java Servlet中的HttpServletRequestWrapper

        Java Servlet中有个类叫HttpServletRequestWrapper,这个类包装了ServletRequest,可以做一个功能的增强,通过类的描述也可以看到,方法模式是调用HttpServletRequest请求,但是我们可以继续包装这个类进行功能的增强。


/**
 * 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);
    }

Java的InputStream、Reader、OutputStream、Writer家族

        先看一下类图,InputStream和OutputStream是抽象组件,下图中所有的类都是可以被包装的,还有一些类没有放过来。另外比如Writer、Reader都是一样的设计思想,只是一个是字节操作,一个是字符操作。但是这也有会有一个问题,就是类爆炸。

         下面是InputStream和FilterInputSteam的主要代码,从代码可以看到FilterInputStream是一个装饰者,同时FilterInputStream的子类BufferedInputStream也是装饰者,跟剥洋葱一样,会发现一层一层又一层都是一个模式,众里寻她千百度,蓦然回首,却在灯火阑珊处。


/**
 * 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.
     */
    这是被包装的输入流
    protected volatile InputStream in;

    通过构造器将包装类传入
    protected FilterInputStream(InputStream in) {
        this.in = in;
    }
    装饰类的方法,这里可以做自定义的扩展
    public int read() throws IOException {
        return in.read();
    }

Dubbo中的Wrapper

        再举个例子,有时候在研发过程中,我们会发现环境毕竟痛苦,这时需要构造一些Mock类,来模拟真实的调用,这时,装饰器的作用就体现出来了。比如Dubbo中的核心类Cluster类,该类将Directory中的多个Invoker伪装成一个Invoker, 对上层透明,包含集群的容错机制。原始的Cluster有以下子类(省略了部分,比如常见的FailFast、FailSafe、FailOver)。而我们关注的是MockClusterWrapper类,这就是对Cluster进行了包装,并返回一个MockClusterInvoker,来进行一些mock操作。

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

}

        其他的还有如ShardingSphere里面的DataSourceWrapper,是对javax.sql.DataSource进行了包装,这里写法和思路都类似,就不一一举例了。

使用场景

  • 在不使用其他对象的情况下,以动态、透明的方式给单个对象添加职责。
  • 处理那些可以撤销的职责。
  • 当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。不能采用继承的情况主要有两类:第一类是系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长;第二类是因为类定义不能继承(如final类)

优缺点对比

优点

  • 比多重继承更加灵活,与对象的继承相比,添加了更加灵活的向对象添加职责的方式。可以用添加和分离的方法,用装饰在运行时刻增加和删除职责。使用Decorator可以很容易地重复添加特性,比如double double的星巴克牛奶。
  • 避免在层次结构高层的类有太多特性。完全不用感知其他的已经定制了的类,相反我们可以定义简单的类,并且用Decorator类给它逐渐添加功能。

缺点

  • 会产生很多小对象,如果过度使用,会让程序复杂。
  • 这种比继承更加灵活机动的特性,尽管对于那些了解这些系统的人来说,很容易对它们进行定制,但是很难学习这些系统,排错也很困难。对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为烦琐。

参考资料

设计模式之禅

Gof设计模式:可复用面向对象软件的基础 典藏版

Head_First设计模式

装饰模式


版权声明:本文为kimichen123原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。