JAVA设计模式-结构型-装饰者模式


一、什么是装饰者模式

装饰者模式就是在不改变原有对象的原则下,动态的对原对象实现功能的可插拔式扩展.
个人理解就是对功能实现模块化和组件化.
生活中的例子:
原始对象:一辆自行车
相关组件:小黄鸭饰品,车载手电筒,后座载人车架等等
执行策略:自行车+车载手电筒,自行车+小黄鸭饰品,自行车+后座载人车架+车载手电筒 等等
说明:这里的自行车就是原始对象,饰品,手电,车架都属于装饰者,使用者可以对这些组件任意搭配来达到增强自行车功能的效果.

二、为什么要使用装饰者模式

举一个不是特别恰当的例子(只讨论用装饰者模式实现):
需求:
假设你在基础模块设计了一个返回类(ResultVO),这个类目前已经很多模块在引用了,然后现在有俩个新的需求:
1.需要你在每次返回数据之前输出一下日志,便于排查问题
2.需要你在每次返回数据之前,向数据中添加一个时间戳参数再返回给前端
但是这俩个功能并不是所有模块都需要,有的模块可能是需要打印日志,有的模块只需要返回时间戳,有的模块既需要打印日志也需要返回时间戳
分析:
1.因为这个返回类已经很多模块在依赖使用,并且每个模块的需求不一样,所以不能直接在返回类中修改代码,不然所有的模块会都受到影响
2.所以这里使用装饰者模式,定义俩个返回类的装饰者类,一个去输出日志,一个去添加时间戳参数
3.这样不同的模块之间就可以任意组合使用了.
总结:
这就是装饰者模式的基本思想,可以动态的在原始对象上添加一些功能,来实现扩展.

上面这个需求例子的代码实现在下面的代码示例01中完成.

三、代码示例

1.代码示例01

这个案例并不是特别切合实际,只是为了单纯实现装饰者模式而写

//定义一个返回类的接口
public interface IResult {
    ResultVO success(Object data);
}
//返回类接口的具体实现
@Data
public class DefaultResult implements IResult {
    @Override
    public ResultVO success(Object data) {
        ResultVO resultVO = new ResultVO();
        resultVO.setCode("200");
        resultVO.setMsg("成功!");
        resultVO.setData(data);
        return resultVO;
    }
}
//返回类对象
@Data
public class ResultVO {
    private Object data;
    private String code;
    private String msg;
}
//返回类的装饰者对象-针对打印日志进行装饰
@Slf4j
public class LoggingResultWrapper implements IResult {

    private final IResult result;

    public LoggingResultWrapper(IResult result) {
        this.result = result;
    }

    @Override
    public ResultVO success(Object data) {
        ResultVO resultVO = result.success(data);
        log.info("result:{}", JSONUtil.toJsonStr(resultVO));
        return resultVO;
    }
}
//返回类的装饰者对象-针对返回数据加强进行装饰
public class EnhanceResultWrapper implements IResult {

    private final IResult result;

    public EnhanceResultWrapper(IResult result) {
        this.result = result;
    }

    @Override
    public ResultVO success(Object data) {
        ResultVO resultVO = result.success(data);
        Object newData = resultVO.getData();
        JSONObject jsonObject = JSONUtil.parseObj(newData);
        jsonObject.set("currentTimeMillis", System.currentTimeMillis());
        resultVO.setData(jsonObject.toBean(Object.class));
        return resultVO;
    }
}
//Main-Client
public class ResultWrapperDemo {

    public static void main(String[] args) {
        Map<String, String> map = new HashMap<>();
        map.put("1", "a");
        map.put("2", "b");

        resultVO(map);
        mode1ResultVO(map);
        mode2ResultVO(map);
        mode3ResultVO(map);
    }

    /**
     * 最原始的返回数据方法
     *
     * @param data
     * @return
     */
    public static ResultVO resultVO(Object data) {
        DefaultResult defaultResult = new DefaultResult();
        return defaultResult.success(data);
    }

    /**
     * 模块1 需要在返回数据时打印日志
     *
     * @param data
     * @return
     */
    public static ResultVO mode1ResultVO(Object data) {
        LoggingResultWrapper loggingResultWrapper = new LoggingResultWrapper(new DefaultResult());
        return loggingResultWrapper.success(data);
    }

    /**
     * 模块2 需要在返回数据时插入时间戳参数
     *
     * @param data
     * @return
     */
    public static ResultVO mode2ResultVO(Object data) {
        EnhanceResultWrapper enhanceResultWrapper = new EnhanceResultWrapper(new DefaultResult());
        return enhanceResultWrapper.success(data);
    }

    /**
     * 模块3 既需要在返回数据时插入时间戳参数 也需要返回数据时打印日志
     *
     * @param data
     * @return
     */
    public static ResultVO mode3ResultVO(Object data) {
        LoggingResultWrapper loggingResultWrapper = new LoggingResultWrapper(new EnhanceResultWrapper(new DefaultResult()));
        return loggingResultWrapper.success(data);
    }
}

这样不同的模块想要什么功能就对应着使用装饰对象去扩展原始对象就可以了.

四、在源码中的应用

1.装饰者模式在Mybatis框架中的应用

Mybatis框架在缓存模块中使用到了装饰者模式

1.Cache接口UML图(org.apache.ibatis.cache)
Cache接口的UMl图
通过UML图可知,Cache接口是有很多实现类的,但是通过查看源码之后发现只有实现类PerpetualCache是真正去实现缓存功能的类,其余的实现类皆为装饰类.

根据PerpetualCache类的代码可知,Mybatis的缓存模块的底层实现其实就是简单的使用一个Map集合来维护的,也就是说在操作Mybatis的缓存时,其实就是在操作这个Map集合而已.

public class PerpetualCache implements Cache {

  private final String id;

  private final Map<Object, Object> cache = new HashMap<>();

  public PerpetualCache(String id) {
    this.id = id;
  }

  @Override
  public String getId() {
    return id;
  }

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

  @Override
  public void putObject(Object key, Object value) {
    cache.put(key, value);
  }

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

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

  @Override
  public void clear() {
    cache.clear();
  }

  @Override
  public boolean equals(Object o) {
    if (getId() == null) {
      throw new CacheException("Cache instances require an ID.");
    }
    if (this == o) {
      return true;
    }
    if (!(o instanceof Cache)) {
      return false;
    }

    Cache otherCache = (Cache) o;
    return getId().equals(otherCache.getId());
  }

  @Override
  public int hashCode() {
    if (getId() == null) {
      throw new CacheException("Cache instances require an ID.");
    }
    return getId().hashCode();
  }
}

然后我们选取其中几个装饰类来举例

先看最简单的LoggingCache装饰类,这个装饰类其实很简单,就是在调用getObject()方法的时候输入了一下日志而已,并没有其他的任何操作,也就是说在使用LoggingCache去装饰Cache对象时,他就在原本的基础上多了一个日志打印的功能.

public class LoggingCache implements Cache {

  private final Log log;
  private final Cache delegate;
  protected int requests = 0;
  protected int hits = 0;

  public LoggingCache(Cache delegate) {
    this.delegate = delegate;
    this.log = LogFactory.getLog(getId());
  }

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

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

  @Override
  public void putObject(Object key, Object object) {
    delegate.putObject(key, object);
  }

  @Override
  public Object getObject(Object key) {
    requests++;
    final Object value = delegate.getObject(key);
    if (value != null) {
      hits++;
    }
    //在日志级别为DuBug时,输出一下日志
    if (log.isDebugEnabled()) {
      log.debug("Cache Hit Ratio [" + getId() + "]: " + getHitRatio());
    }
    return value;
  }

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

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

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

  @Override
  public boolean equals(Object obj) {
    return delegate.equals(obj);
  }

  private double getHitRatio() {
    return (double) hits / (double) requests;
  }
}

然后再看一下SynchronizedCache装饰类,这个装饰类其实也很简单,就是在相关的方法上增加了一个synchronized关键字而已,也就是说在使用SynchronizedCache去装饰Cache对象时,他就在原本的基础上多了一个加锁的功能.

public class SynchronizedCache implements Cache {

  private final Cache delegate;

  public SynchronizedCache(Cache delegate) {
    this.delegate = delegate;
  }

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

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

  @Override
  public synchronized void putObject(Object key, Object object) {
    delegate.putObject(key, object);
  }

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

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

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

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

  @Override
  public boolean equals(Object obj) {
    return delegate.equals(obj);
  }

}

那么这里就举例这俩个比较简单的装饰类,其他复杂的装饰类只是实现的功能复杂而已,装饰者模式的思想是一样的.
因为本文主要是研究的是装饰者模式,所以那些比较复杂功能的装饰类就不去研究了,避免喧宾夺主,各位小伙伴有兴趣的可以自行去看看相关源码.

五、总结

看到这里各位小伙伴应该也能发现,装饰者模式其实并不是一个很复杂的设计模式,运用起来的话也相对还是比较容易的.
说白了就是针对一个基础的原始对象做了一系列可插拔式的组件,然后根据实际需求灵活套用就可以了.

最后还是说一句,设计模式主要是学习其中的思想,在实际的开发中千万不要生搬硬套,否则可能会极大的增加系统的复杂度,在使用某种设计模式之前一定要多考虑可行性.

至此,JAVA装饰者模式就梳理完毕了!~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值