一、饰器者模式介绍
 
装饰者模式定义:在不改变原有对象的基础上附加功能,相比生成子类更灵活。
 
适用场景:动态的给一个对象添加或者撤销功能。
 
优点:可以不改变原有对象的情况下动态扩展功能,可以使扩展的多个功能按想要的顺序执行,以实现不同效果。
 
缺点:更多的类,使程序复杂
 
类型:结构型。
 
类图:
 

 
源码分析中的典型应用
 
 
 - Java I/O 中的装饰者模式
- Spring Session 中的装饰者模式
- Mybatis 缓存中的装饰者模式
二、给系统添加日志,安全、限流示例
 
一般系统的安全、日志、限流等业务无关代码可以抽离出来,在Controller前后用切面改造,模板方法模式可以部分解决这个问题:
 
public abstract class BaseAspect {
    Logger logger = LoggerFactory.getLogger(BaseCommand.class);
    public void execute(){
	    //记录日志
        logger.debug("..start..");
       //过滤跨站脚本攻击
       paramXssAspect();
        //限制速率
        doRateLimit();
        doBusiness();
        logger.debug("..end..");
    }
    public abstract void doBusiness();
}
class PlaceOrderAspect extends BaseAspect {
    @Override
    public void doBusiness() {
        //下单操作
    }
}
class PayOrderAspect extends BaseAspect {
    @Override
    public void doBusiness() {
        //支付操作
    }
}
 
在父类中已经把”乱七八糟“的非业务代码写好了,只留了一个抽象方法等子类去实现,子类变的很清爽,只需关注业务逻辑就可以了。
 
这种方式最大的缺陷就是父类会定义一切:要执行那些非业务代码,以什么顺序执行等等,子类只能无条件接受。如果有一个子类,不限制速率,那么它也没有办法把它去掉。
 
利用装饰者模式,针对上面的问题,可以变的很灵活。
 
//最高层抽象组件
interface IAspect {
    String doHandlerAspect();
}
//基本被装饰类,做一些公共处理
class AspectImpl implements IAspect{
    @Override
    public String doHandlerAspect() {
        return "裸跑代码.";
    }
}
abstract class AbstractDecorator implements IAspect{
    //很重要,组合抽象构件到自己的类中
    private IAspect aspect;
    public AbstractDecorator(IAspect aspect) {//通过IAspect构造自己
        this.aspect = aspect;
    }
    @Override
    public String doHandlerAspect() {
        return this.aspect.doHandlerAspect();
    }
}
 
附加记录日志,安全,限流功能:
 
class LoggerAspect extends  AbstractDecorator{
    public LoggerAspect(IAspect aspect){
        super(aspect);
    }
    @Override
    public String doHandlerAspect() {
        return super.doHandlerAspect()+"+记录日志.";
    }
}
class ParamXssAspect extends  AbstractDecorator{
    public ParamXssAspect(IAspect aspect){
        super(aspect);
    }
    @Override
    public String doHandlerAspect() {
        return super.doHandlerAspect()+"+过滤危险字符.";
    }
}
class LimitAspect extends  AbstractDecorator{
    public LimitAspect(IAspect aspect){
        super(aspect);
    }
    @Override
    public String doHandlerAspect() {
        return super.doHandlerAspect()+"+限流.";
    }
}
 
测试一下:
 
public class Test {
    public static void main(String[] args) {
        IAspect aspect = new LimitAspect(new ParamXssAspect(new LoggerAspect(new AspectImpl())));
        System.out.println(aspect.doHandlerAspect());
    }
}
 
运行结果:
 
------
 
裸跑代码.+记录日志.+过滤危险字符.+限流.
 
------
 
通过上面可以看出,装饰者模式可以任意次序组装功能,是不是很灵活?另外,也可以把上述三个功能封装成注解@Log、@ParamXss、@AccessLimit,实现可拔插。如果读者想看注解功能完整实现,可以参考我的这个项目:SpringBoot+JWT+Shiro+MybatisPlus实现Restful快速开发后端脚手架
 
三、源码中的装饰者模式
 
3.1、Java IO中是体现最明显的装饰者模式。
 
它基于字符流(InputStream/OutputStream) 和 字节流(Reader/Writer)作为基类,下面画出InputStream、Reader的部分类图:
 

 

 
这里总结几种常用流的应用场景:
 
 
  
   
   | 流名称 | 应用场景 | 
 
  
  
   
   | ByteArrayInputStream | 访问数组,把内存中的一个缓冲区作为 InputStream 使用,CPU从缓存区读取数据比从存储介质的速率快10倍以上 | 
 
   
   | StringBufferInputStream | 把一个 String 对象作为。InputStream。不建议使用,在转换字符的问题上有缺陷 | 
 
   
   | FileInputStream | 访问文件,把一个文件作为 InputStream ,实现对文件的读取操作 | 
 
   
   | PipedInputStream | 访问管道,主要在线程中使用,一个线程通过管道输出流发送数据,而另一个线程通过管道输入流读取数据,这样可实现两个线程间的通讯 | 
 
   
   | SequenceInputStream | 把多个 InputStream 合并为一个 InputStream . “序列输入流”类允许应用程序把几个输入流连续地合并起来 | 
 
   
   | DataInputStream | 特殊流,读各种基本类型数据,如byte、int、String的功能 | 
 
   
   | ObjectInputStream | 对象流,读对象的功能 | 
 
   
   | PushBackInputStream | 推回输入流,可以把读取进来的某些数据重新回退到输入流的缓冲区之中 | 
 
   
   | BufferedInputStream | 缓冲流,增加了缓冲功能 | 
 
  
 
3.2、Spring Session中的ServletRequestWrapper(Response也一样)的装饰者模式。
 
public class ServletRequestWrapper implements ServletRequest {
    private ServletRequest request;//组合抽象接口到自己的类中
    public ServletRequestWrapper(ServletRequest request) {//可以构造自己
        if(request == null) {
            throw new IllegalArgumentException("Request cannot be null");
        } else {
            this.request = request;
        }
    }
    public ServletRequest getRequest() {
        return this.request;
    }
    public void setRequest(ServletRequest request) {
        if(request == null) {
            throw new IllegalArgumentException("Request cannot be null");
        } else {
            this.request = request;
        }
    }
   //省略...
}
 
3.3、Spring Cache中的TransactionAwareCacheDecorator的装饰者模式。
 
其实从类名就可以看出。
 
public class TransactionAwareCacheDecorator implements Cache {
    private final Cache targetCache;//把Cache组合到自己类中
    public TransactionAwareCacheDecorator(Cache targetCache) {//通过Cache构造自己
        Assert.notNull(targetCache, "Target Cache must not be null");
        this.targetCache = targetCache;
    }
    public <T> T get(Object key, Class<T> type) {
        return this.targetCache.get(key, type);
    }
    public void put(final Object key, final Object value) {
        // 判断是否开启了事务
        if (TransactionSynchronizationManager.isSynchronizationActive()) {
            // 将操作注册到 afterCommit 阶段
            TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
                public void afterCommit() {
                    TransactionAwareCacheDecorator.this.targetCache.put(key, value);
                }
            });
        } else {
            this.targetCache.put(key, value);
        }
    }
    // ...省略...
}
 
3.4、Mybatis中的装饰者。
 
Cache为抽象构件类,PerpetualCache为具体构件类,decorators包下的类为装饰类,这里没有抽象装饰类。
 

 
参考:
 
设计模式 | 装饰者模式及典型应用
 
《码农翻身》刘欣