结构型-装饰模式


一、介绍

装饰模式是一种结构型设计模式, 允许你通过将对象放入包含行为的特殊封装对象中来为原对象绑定新的行为。

其实是装饰对象和被装饰对象都实现了同一个接口,装饰对象有一个成员变量指向被装饰对象,在调用装饰对象方法时,执行装饰者的扩展方法后,会再调用被装饰者的方法,从而实现了对被装饰者功能的扩展

二、MyBatis 二级缓存执行器

这里以 MyBatis 的二级缓存执行器 CachingExecutor 装饰 基础执行器 BaseExecutor 为例

1、接口

public interface Executor {
    <E> List<E> query(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4, CacheKey var5, BoundSql var6) throws SQLException;
}

2、被装饰类

public abstract class BaseExecutor implements Executor {

    @Override
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        // 执行查询逻辑,一级缓存查不到,则去查 DB
    }
}

3、装饰类

public class CachingExecutor implements Executor {
    private final Executor delegate;
    private final TransactionalCacheManager tcm = new TransactionalCacheManager();

    public CachingExecutor(Executor delegate) {
        this.delegate = delegate;
        delegate.setExecutorWrapper(this);
    }

    public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        // 增强逻辑,查询二级缓存...

        // 如果查询不到二级缓存则执行被装饰者 BaseExecutor 去查询一级缓存或DB
        return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }
}

三、Java IO

image-20221216134521138

InputStream 代表了输入流,具体的输入来源可以是文件(FileInputStream)、管道(PipedInputStream)、数组(ByteArrayInputStream)等

FilterInputStream 承接了装饰模式的关键节点,它的实现类是一系列装饰器,比如 BufferedInputStream 代表用缓冲来装饰,也就使得输入流具有了缓冲的功能,LineNumberInputStream 代表用行号来装饰,在操作的时候就可以取得行号了,DataInputStream 的装饰,使得我们可以从输入流转换为 java 中的基本类型值。

public class FilterInputStream extends InputStream {

    protected volatile InputStream in;

    protected FilterInputStream(InputStream in) {
        this.in = in;
    }

    ...
}

当然,在 java IO 中,如果我们使用装饰器的话,就不太适合面向接口编程了,如:

InputStream inputStream = new LineNumberInputStream(new BufferedInputStream(new FileInputStream("")));

这样的结果是,InputStream 还是不具有读取行号的功能,因为读取行号的方法定义在 LineNumberInputStream 类中。

我们应该像下面这样使用:

DataInputStream is = new DataInputStream(
                              new BufferedInputStream(
                                  new FileInputStream("")));

所以说嘛,要找到纯的严格符合设计模式的代码还是比较难的。

四、使用场景

如果你希望在无需修改代码的情况下即可使用对象, 且希望在运行时为对象新增额外的行为, 可以使用装饰模式。

装饰能将业务逻辑组织为层次结构, 你可为各层创建一个装饰, 在运行时将各种不同逻辑组合成对象。 由于这些对象都遵循通用接口, 客户端代码能以相同的方式使用这些对象。

如果用继承来扩展对象行为的方案难以实现或者根本不可行, 你可以使用该模式。

许多编程语言使用 final最终关键字来限制对某个类的进一步扩展。 复用最终类已有行为的唯一方法是使用装饰模式: 用封装器对其进行封装。


  目录