装饰者模式在mybatis框架中的应用

一、什么是装饰者模式

    装饰者模式(Decorator Pattern)是指在不改变原有对象的基础之上,将功能附加到对象上,提供了比继承更有弹性的替代方案(扩展原有对象的功能),属于结构型模式。

    《Head First设计模式》书中有具体的介绍,还有例子说明,可以作为学习入门的参考。以下为对应的链接

    https://blog.csdn.net/qq_33404395/article/details/80510909

    装饰者模式在Java中的应用,最有名的莫过于I/O标准库的设计,网上也有大量的资料可以作为装饰者模式学习的参考。本文章主要还是以装饰者模式在mybatis框架中的应用做一下总结。

二、装饰者模式的应用

1、mybatis的缓存机制

    首先,让我们来看下mybatis的缓存机制的接口,以及其接口实现。
在这里插入图片描述
    接口Cache,去除注释后,可以清晰的看到,其缓存机制就是采用的以下几个接口方法去实现的。

public interface Cache {

  String getId();

  void putObject(Object key, Object value);

  Object getObject(Object key);

  Object removeObject(Object key);

  void clear();

  int getSize();

  default ReadWriteLock getReadWriteLock() {
    return null;
  }

}

    对应的实现类分批进行截图
在这里插入图片描述
在这里插入图片描述
​    其实从实现类的命名,也可以大致猜到各个实现类的作用,或者起到的装饰作用是什么,那么装饰器模式必然有一个是具体实现功能的,其他的作为装饰器对其进行增强,而框架集成过程中又会通过配置的形式,在项目启动的时候,根据配置选择对应的装饰器进行装饰。那先来看下具体的缓存实现类PerpetualCache,相当简单粗暴的实现方法。缓存机制采用的其实就是HashMap进行实现的,由于代码比较简单,不过多进行介绍,因为这个不是重点。

public class PerpetualCache implements Cache {

  private final String id;

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

}

    通过实现类可以看出,框架一共提供了十个装饰器去装饰这一个具体的实现。

    以mybatis缓存机制实现的类图来对该设计模式进行说明,这里以LoggingCache举例:
在这里插入图片描述
    Cache定义了缓存机制所需要实现的接口,PerpetualCache作为缓存机制具体的实现类,其底层进行了缓存的具体实现。然而这个类也仅仅只是实现了最基础的功能,如果我们现在需要实现一个日志的实现,那么在PerpetualCache中进行添加,那是相当糟糕的做法,因为后面可能会有更多的需求,不断的添加会使得这个类显得很冗余且不灵活,而且没法很好的进行扩展,例如:我现在不想要日志功能,那么可能就要删除代码处理。这样的代码迭代将会很糟糕,而且这不符合软件设计的开闭原则,采用装饰者模式就很好的解决了这个问题。

    查看LoggingCache的构造方法,通过构造方法传参传入delegate对象,而该对象既是Cache,通过在各个方法中进行装饰增强。
在这里插入图片描述
    查看LoggingCache代码不难看出,LoggingCache只是对于getObject方法进行了命中日志的实现,而且采用的是debug日志。装饰者模式最终底层还是调用了被装饰者delegate,这个就是构造方法中传入的delegate对象,然后,在该对象调用具体getObject方法的同时,对其进行日志的装饰。

  public Object getObject(Object key) {
    requests++;
    final Object value = delegate.getObject(key);
    if (value != null) {
      hits++;
    }
    // 日志实现
    if (log.isDebugEnabled()) {
      log.debug("Cache Hit Ratio [" + getId() + "]: " + getHitRatio());
    }
    return value;
  }

    通过配置的形式,最终通过CacheBuilder进行创建,该类明显的采用了建造者模式,build代码如下:

   private final List<Class<? extends Cache>> decorators;
   public Cache build() {
    setDefaultImplementations();
    Cache cache = newBaseCacheInstance(implementation, id);
    setCacheProperties(cache);
    // issue #352, do not apply decorators to custom caches
    if (PerpetualCache.class.equals(cache.getClass())) {
      for (Class<? extends Cache> decorator : decorators) {
        cache = newCacheDecoratorInstance(decorator, cache);
        setCacheProperties(cache);
      }
      cache = setStandardDecorators(cache);
    } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
      cache = new LoggingCache(cache);
    }
    return cache;
  }

    newBaseCacheInstance方法实现了实现类的创建,通过类反射机制,通过字符串id传参创建,Cache的默认实现类中,只有PerpetualCache的创建方法是字符串传参,如果设置的实现类是PerpetualCache,则会将设置的decorators装饰者对Cache进行装饰。
在这里插入图片描述
    CacheBuilder的build方法由MapperBuilderAssistant进行调用,查看建造代码:

  public Cache useNewCache(Class<? extends Cache> typeClass,
      Class<? extends Cache> evictionClass,
      Long flushInterval,
      Integer size,
      boolean readWrite,
      boolean blocking,
      Properties props) {
    Cache cache = new CacheBuilder(currentNamespace)
        .implementation(valueOrDefault(typeClass, PerpetualCache.class))
        .addDecorator(valueOrDefault(evictionClass, LruCache.class))
        .clearInterval(flushInterval)
        .size(size)
        .readWrite(readWrite)
        .blocking(blocking)
        .properties(props)
        .build();
    configuration.addCache(cache);
    currentCache = cache;
    return cache;
  }

    由这一部分代码可以看出,默认创建的就是PerpetualCache类的实现,而默认会添加LruCache进行装饰,该类采用了lru算法的实现。

    继续往上追踪,可以看出其配置提供了两种方式,注解形式,和xml形式,读取配置不详细涨开。
在这里插入图片描述

2、mybatis的缓存机制采用装饰者模式设计的优缺点

    优点:

    1、开闭原则,对扩展开放,对修改关闭。如果要实现一个新的功能,则实现对应的接口,并配置对应的装饰者,而这一切并不需要改动原有代码的实现。

    2、单一职责原则,具体的缓存实现由PerpetualCache具体的类实现,其他功能,例如二级缓存,日志,序列化等等由具体的装饰者实现。各个类实现各个类所需要实现的具体功能。

    3、合成复用原则,mybatis的缓存机制很好的可以通过配置,选取对应的组合来实现具体的缓存功能,而非采用继承的关系达到代码复用。假设采用继承的方式,那么各种组合,将会产生大量的类,而这样即不方便管理,且显得冗余。

    缺点:

    1、装饰者可能只需要对某个方法进行装饰,例如LoggingCache仅装饰getObject,可是却需要实现其他方法。

    2、层层嵌套,比较难以排错,不过只要控制好对应的日志,还有报错,这个问题不大,例如缓存报错会抛出CacheException,而该异常的message可以作为后续定位问题的关键。

  • 6
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值