原创

设计模式(十六):装饰器模式(Decorator)

装饰器模式 又称 包装模式。装饰模式以对客户透明的方式,在不改变对象结构的情况下,可以动态地扩展其功能。

装饰器模式是继承关系的一个替代方案,可以在不使用创造更多子类的情况下,扩展对象的功能。

模式定义

装饰器(Decorator)模式:指在不改变现有对象结构的情况下,动态地扩展该对象的功能的模式,它属于对象结构型模式,是对现有对象的一个包装。

该模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。相比创建子类的方式更为灵活。

模式分析

模式结构

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

  • 抽象构件(Component)角色:定义一个抽象接口以规范准备接收扩展功能的目标对象。
  • 具体构件(ConcreteComponent)角色:实现抽象构件,负责具体的行为实现。
  • 抽象装饰(Decorator)角色:实现抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
    注意:Decorator 角色在实际应用中不一定是抽象类,因它的功能是一个抽象角色,才称它为抽象装饰
  • 具体装饰(ConcreteDecorator)角色:继承抽象装饰角色,传入具体构件对象给到父类,重写抽象构件的方法,在重写的方法里面给具体构件对象添加附加的责任。

主要优缺点

优点:

  • 装饰器是继承的有力补充,比继承灵活。装饰类和被装饰类可以独立发展,不会相互耦合,在不改变原有对象的情况下,可以动态扩展一个实现类的功能,即插即用。

  • 通过使用装饰类及这些装饰类的排列组合,可以实现不同效果。

    排列组合指:在有多个装饰器时,按排列组合,将具体装饰器作为其它装饰器的构造方法入参(当作具体部件),组成装饰器嵌套,实现复杂的的附加功能。

  • 装饰器模式完全遵循关闭原则。

缺点:可能会增加许多具体装饰子类,甚至多层装饰,增加程序的复杂性,不易于理解。

模式结构图

装饰器模式结构图

结构代码示例

抽象构件:构件接口类

/**
 * @author gxing
 * @desc 抽象构件
 */
public interface Component {
    void method();
}

抽象构件具体实现:抽象构件接口的具体实现类

/**
 * @author gxing
 * @desc 抽象构件具体实现
 */
public class ConcreteComponentA implements Component {

    @Override
    public void method() {
        System.out.println("Component:ConcreteComponentA");
    }
}

/**
 * @author gxing
 * @desc 抽象构件具体实现
 */
public class ConcreteComponentB implements Component {
    @Override
    public void method() {
        System.out.println("Component: ConcreteComponentB");
    }
}

抽象装饰器:实现抽象构件接口

/**
 * @author gxing
 * @desc 抽象装饰器, 实现抽象构件
 */
public abstract class Decorator implements Component {

    // 包含抽象构件的具体实现
    protected Component component;

    /**
     * 构造传入抽象构件的具体实现
     *
     * @param component
     */
    public Decorator(Component component) {
        this.component = component;
    }

    /**
     * 抽象构件的具体实例调用操作方法
     */
    public void method() {
        // 委派给具体实例执行
        component.method();
    }
}

具体装饰器:实现抽象装饰器,重写操作方法,在操作方法里附加功能

/**
 * @author gxing
 * @desc 具体装饰器, 继承抽象装饰器,
 */
public class ConcreteDecorator extends Decorator {

    /**
     * 构造传入抽象构件的具体实现, 传给父类包装器
     *
     * @param component
     */
    public ConcreteDecorator(Component component) {
        super(component);
    }

    /**
     * 具体装饰器,重写父类装饰器的默认方法
     * 在该方法里给具体构件附加功能
     */
    @Override
    public void method() {
        super.component.method();
        addFunction(super.component);
    }

    /**
     * 附加的功能
     *
     * @param component
     */
    private void addFunction(Component component) {
        System.out.println("ConcreteComponent");
    }
}

简化版示例

装饰器模式所包含的 4 个角色不是任何时候都要存在的,在有些应用环境下模式是可以简化的。

  • 没有抽象构件角色,只有具体类,具体装饰器就需要继承具体类
  • 没有抽象装饰角色,只有具体装饰类,就需要将抽象装饰与具体装饰合并

没有抽象构件

具体装饰器就需要继承具体类

结构图

装饰器模式结构图

具体构件

/**
 * @author gxing
 * @desc 具体构件
 */
public class ConcreteComponent {

    public void method() {
        System.out.println("Component:ConcreteComponent");
    }
}

抽象装饰器

/**
 * @author gxing
 * @desc 抽象装饰器, 实现抽象构件
 */
public abstract class Decorator extends ConcreteComponent {

    // 包含抽象构件的具体实现
    protected ConcreteComponent component;

    /**
     * 构造传入抽象构件的具体实现
     *
     * @param component
     */
    public Decorator(ConcreteComponent component) {
        this.component = component;
    }

    /**
     * 抽象构件的具体实例调用操作方法
     */
    public void method() {
        component.method();
    }
}

具体装饰器

/**
 * @author gxing
 * @desc 具体装饰器, 继承抽象装饰器,
 * @date 2021/2/22
 */
public class ConcreteDecorator extends Decorator {

    /**
     * 构造传入抽象构件的具体实现, 传给父类包装器
     *
     * @param component
     */
    public ConcreteDecorator(ConcreteComponent component) {
        super(component);
    }

    /**
     * 具体装饰器,重写父类装饰器的默认方法
     * 在该方法里给具体构件附加功能
     */
    @Override
    public void method() {
        component.method();
        addFunction(component);
    }

    /**
     * 附加的功能
     *
     * @param component
     */
    private void addFunction(ConcreteComponent component) {
        System.out.println("ConcreteComponent");
    }
}

没有抽象装饰器

只有一个具体装饰器,可以将抽象装饰和具体装饰合并,具体装饰器直接实现 抽象构件

装饰器模式结构图

抽象构件

/**
 * @author gxing
 * @desc 抽象部件
 */
public interface Component {
    void method();
}

具体构件

/**
 * @author gxing
 * @desc 抽象部件具体实现
 */
public class ConcreteComponentA implements Component {

    @Override
    public void method() {
        System.out.println("Component:ConcreteComponentA");
    }
}

/**
 * @author gxing
 * @desc 抽象部件具体实现
 */
public class ConcreteComponentB implements Component {
    @Override
    public void method() {
        System.out.println("Component: ConcreteComponentB");
    }
}

具体装饰器

/**
 * @author gxing
 * @desc 具体装饰器, 继承抽象装饰器,
 * @date 2021/2/22
 */
public class ConcreteDecorator {

    // 包含抽象部件的具体实现
    protected Component component;

    /**
     * 构造传入抽象部件的具体实现, 传给父类包装器
     *
     * @param component
     */
    public ConcreteDecorator(Component component) {
        this.component = component;
    }

    /**
     * 具体装饰器,重写父类装饰器的默认方法
     * 在该方法里给具体部件附加功能
     */
    public void method() {
        component.method();
        addFunction(component);
    }

    /**
     * 附加的功能
     *
     * @param component
     */
    private void addFunction(Component component) {
        System.out.println("ConcreteComponent");
    }
}

客户端使用

// 传入具体的构件
Component component = new ConcreteDecorator(new ConcreteComponent());
component.operation();

适用场景

装饰器模式通常在以下几种情况适用:

  • 需要扩展一个现有类的功能,或给一个类增加附加责任,而又不能采用生成子类的方法进行扩充时。
    例如,该类被隐藏或者该类是终极类或者采用继承方式会产生大量的子类。
  • 需要通过对现有的一组基本功能进行排列组合而产生非常大量的功能,采用继承关系很难实现,而采用装饰器模式却很好实现。
  • 需要动态地给一个对象增加功能,可以再动态地撤销这些功能。

典型应用

在JDK中应用

装饰器模式在 Java I/O 标准库(java.io 包)包中广泛使用。例如,基于字节流的 InputStream/OutputStream 和基于字符的 Reader/Writer 体系。

  • InputStream 的子类 FilterInputStream
  • OutputStream 的子类 FilterOutputStream
  • Reader 的子类 BufferedReader 以及 FilterReader
  • Writer 的子类 BufferedWriter、FilterWriter 以及 PrintWriter 等

以 InputStream 为例,InputStream 是所有字节输入流的基类,其下有众多子类,如基于文件的 FileInputStream、基于对象的ObjectInputStream、基于字节数组的 ByteArrayInputStream 等。有些时候,需要为这些流加一些其他的小特性,如缓冲、压缩等,用装饰模式实现就非常方便。相关的部分类图如下所示。

装饰器模式结构图

  • 抽象构件:InputStream
  • 具体构件:FileInputStream,ObjectInputStream
  • 抽象装饰器:FilterInputStream,继承抽象构件 InputStream,构造方法传入具体构件对象
  • 具体装饰器:FilterInputStream 的所有子类。如,BufferedInputStream

使用:BufferedInputStream 缓存输入流。

public class Client {
    public static void main(String[] args) throws IOException {

        OutputStream os = new BufferedOutputStream(new ByteArrayOutputStream());
        InputStream in = new BufferedInputStream(new ByteArrayInputStream("HelloWorld".getBytes()), 1024);

        byte[] buff = new byte[2048];
        int pos;
        while (-1 != (pos = in.read(buff, 0, buff.length))){
            os.write(buff, 0, pos);
        }
    }
}

在Spring中应用

Spring 中用到的装饰器模式在类名上有两种表现:一种是类名中含有 Wrapper,另一种是类名中含有 Decorator。

Spring 的 TransactionAwareCacheDecorator 是 处理 Spring 有事务的时候缓存的类。在使用 Spring 的 cache 注解实现缓存的时候,当出现事务的时候,对缓存的同步就需要做相应的处理,于是有了这个 事务缓存装饰器

TransactionAwareCacheDecorator 缓存装饰器,这是一个具体装饰器,将抽象装饰和具体装饰合并了。这里应用的是没有抽象装饰的简化版 装饰器模式,具体装饰器直接实现了 抽象构件 Cache 接口,。

TransactionAwareCacheDecorator,同步缓存put,evict,clear操作与 Spring 管理的事务。

执行实际缓存仅在成功提交的后提交阶段执行 put/evict/clear操作交易。如果没有活动的事务, put/evict/clear 操作将像往常一样立即执行。

TransactionAwareCacheDecorator 原码

public class TransactionAwareCacheDecorator implements Cache {

    private final Cache targetCache;


    /**
     * 构造方法传入具体的缓存实现
     */
    public TransactionAwareCacheDecorator(Cache targetCache) {
        Assert.notNull(targetCache, "Target Cache must not be null");
        this.targetCache = targetCache;
    }

    /**
     * 对 cache 的 put 方法进行包装
     * 增加了对事务的支持
     */
    @Override
    public void put(final Object key, @Nullable final Object value) {
        if (TransactionSynchronizationManager.isSynchronizationActive()) {
            TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
                @Override
                public void afterCommit() {
                    TransactionAwareCacheDecorator.this.targetCache.put(key, value);
                }
            });
        }
        else {
            this.targetCache.put(key, value);
        }
    }
}

在Mybatis中应用

Mybatis 也有缓存(Cache),用到了 装饰器模式。Mybatis 支持两级缓存,分别是 session 缓和 mapper 级,在默认情况下是没有开启的。

Mybatis 的执行器(Executor)的 缓存执行器(CachingExecutor)也是使用装饰器模式,对 query,update操作附加了缓存操作功能。

下面以缓存 Cache 的装饰器为例:

Mapper XML 里可以配置 cache 标签来开启缓存,示例如下:

<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>

该配置是创建一个 FIFO 缓存,刷新间隔是 60秒,最大储储512个对象的引用,返回的对象被认为是只读的。

Mybatis 在创建配置 Configuration 对象时会注册所有的缓存装饰器。

Mybaits 的缓存策略有:LRU, FIFO, SOFT, WEAK,每种策略对应一个缓存装饰器。

  • 抽像构件:Cache
  • 具体构件:PerpetualCache
  • 具体装饰器:FifoCache,LruCache,SoftCache,WeakCache

FifoCache 具体装饰器:对缓存对象进行了包装,附加了缓存策略功能。

package org.apache.ibatis.cache.decorators;

import java.util.Deque;
import java.util.LinkedList;

import org.apache.ibatis.cache.Cache;

/**
 * FIFO (first in, first out) cache decorator.
 * 使用 FIFO 先进先出策略对缓存进行包装
 */
public class FifoCache implements Cache {

    private final Cache delegate;
    private final Deque<Object> keyList; // 双端队列
    private int size;

    public FifoCache(Cache delegate) {
        this.delegate = delegate;
        this.keyList = new LinkedList<>();// 双端队列用的是链表
        this.size = 1024;
    }

    @Override
    public String getId() {
        return delegate.getId();
    }

    @Override
    public int getSize() {
        return delegate.getSize();
    }

    public void setSize(int size) {
        this.size = size;
    }

    @Override
    public void putObject(Object key, Object value) {
        // 附加功能实现 FIFO 策略
        cycleKeyList(key);
        delegate.putObject(key, value);
    }

    @Override
    public Object getObject(Object key) {
        return delegate.getObject(key);
    }

    @Override
    public Object removeObject(Object key) {
        return delegate.removeObject(key);
    }

    @Override
    public void clear() {
        delegate.clear();
        keyList.clear();
    }

    private void cycleKeyList(Object key) {
        // 往双端队列的队尾存储 key
        keyList.addLast(key);
        // 如果已满, 则移出队头
        if (keyList.size() > size) {
            Object oldestKey = keyList.removeFirst();
            // 缓存中删除最旧的元素
            delegate.removeObject(oldestKey);
        }
    }
}

与适配器模式的区别

适配器模式 与 装饰器模式都是对 源 接口进行包装以达到增强,但存在区别,如下:

  • 适配器模式的是要将一个接口转变成另一个接口,是通过改变接口来解决接口不兼容问题,达到复用目的。
  • 装饰器模式是在不改变原有接口情况下,给原对象附加功能。所以这两个模式设计的目的是不同的。

相关参考

  1. 装饰器模式(装饰设计模式)详解
正文到此结束
本文目录