装饰者模式?No!少生优生?Yes!

最近在阅读 Mybatis 的源码,文章的讲解以缓存模块为例子。

原来故事在那天是这样开始的......

角色出场

🚺Cache:(集美貌与智慧并存,迷倒万千数据源的缓存接口...)👏ing....方法很多,但是迷倒你们只需要两点,但是买二送一,所以添加缓存、获取缓存、删除缓存

java 复制代码
/**
 * 缓存容器接口
 */

public interface Cache {

  /**
   * 缓存标识
   *
   * @return The identifier of this cache
   */
  String getId();

  /**
   * 添加指定键的值
   */
  void putObject(Object key, Object value);

  /**
   * 获取指定键的值
   *
   * @param key The key
   * @return The object stored in the cache.
   */
  Object getObject(Object key);

  /**
   * 移除指定键的值
   */
  Object removeObject(Object key);

  /**
   * 清空缓存
   *
   * Clears this cache instance
   */
  void clear();
}

🚹PerpetualCache:(风靡缓存模块的大帅哥,一直被模仿,从未被超越!)👏ing....行走江湖就靠对Cache接口里面方法的实现,定义了一个HashMap当做缓存的容器,所谓的增删查可不就是对集合的操作么?

java 复制代码
/**
 * 基础的Cache实现类
 */
public class PerpetualCache implements Cache {

  /**
   * 标识
   */
  private final String id;

    /**
     * 缓存容器
   */
  private Map<Object, Object> cache = new HashMap<>();


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

}

有一只不知天高地厚的程序猿,表示很仰慕Cache,想要实现对缓存命中率的统计,它给自己的类取名叫 LoggingCache ,作为一只精明的程序猿,为了尽可能减少自己的劳动量,对于缓存的添加、删除、获取这些操作已经有实现了,但是又不能明目张胆的偷,所以它决定认老爹。于是,LoggingCache决定继承PerpetualCache,进行代码的复用,然后再实现自己的逻辑。

Java 复制代码
/**
* 支持打印日志的LoggingCache
*/
public class LoggingCache extends PerpetualCache {


 /**
  * 统计请求缓存的次数
  */
 protected int requests = 0;

 /**
  * 统计命中缓存的次数
  */
 protected int hits = 0;


 /**
  * 缓存命中率
  * @return
  */
 private double getHitRatio() {
   return (double) hits / (double) requests;
 }

}

又有一只不知天高地厚的程序猿,也很仰慕Cache,想要实现LRU算法的缓存策略,它给自己的类取名叫 LruCache 。有着前车之鉴,所以它决定直接认老爹。

Java 复制代码
/**
 *  使用LRU算法淘汰:上次使用距离现在最久的key
 */
public class LruCache extends PerpetualCache {

  private Map<Object, Object> keyMap;
  private Object eldestKey;



  public void setSize(final int size) {
    //LinkedHashMap的一个构造函数,当参数accessOrder为true时,即会按照访问顺序排序,最近访问的放在最前,最早访问的放在后面
    keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {
      private static final long serialVersionUID = 4267176411845948333L;

      // LinkedHashMap自带的判断是否删除最老的元素方法,默认返回false,即不删除老数据
      // 我们要做的就是重写这个方法,当满足一定条件时删除老数据
      @Override
      protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
        boolean tooBig = size() > size;
        //达到容量上限,设置删除最老的key
        if (tooBig) {
          eldestKey = eldest.getKey();
        }
        return tooBig;
      }
    };
  }

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

  @Override
  public Object getObject(Object key) {
    keyMap.get(key); //touch
    return delegate.getObject(key);
  }


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


  private void cycleKeyList(Object key) {
    keyMap.put(key, key);
    //判断是否删除最老的key
    if (eldestKey != null) {
      delegate.removeObject(eldestKey);
      eldestKey = null;
    }
  }

}

这个时候,基础实现类 PerpetualCache 觉得子类太多,于是,大帅哥把两个儿子,LoggingCache和LruCache叫到跟前:你们整天窝在家里也不是那么回事,你们不出门打工我小美的包包谁来买?我的房贷谁来还?这样吧,你们都去中天钢铁进厂,我给你们我的电话,小事不要找我,大事找我也没用。

最终,LoggingCache 和 LruCache 被赶出了家门。也从 PerpetualCache 子类中除了名,只能重新投到 Cache 接口门下,并且加上了一个 Cache 属性,创建对象的时候传入 Cache 的实现类。

Java 复制代码
/**
 * 支持打印日志的Cache实现类
 *
 * @author Clinton Begin
 */
public class LoggingCache implements Cache  {

  /**
   * mybatis的Log 对象
   */
  private final Log log;
  /**
   * 装饰的Cache对象
   */
  private final Cache delegate;

  /**
   * 统计请求缓存的次数
   */
  protected int requests = 0;

  /**
   * 统计命中缓存的次数
   */
  protected int hits = 0;

  public LoggingCache(Log log, Cache delegate) {
    this.log = log;
    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++;
    }
    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 ReadWriteLock getReadWriteLock() {
    return null;
  }

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

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

  /**
   * 缓存命中率
   * @return
   */
  private double getHitRatio() {
    return (double) hits / (double) requests;
  }

}

可谓因祸得福,LoggingCache 不仅可以复用基础实现类 PerpetualCache 中关于缓存操作的代码,也能通过调整 Cache 参数,去搭配任何实现了 Cache 接口的实现类。

相关推荐
-曾牛8 小时前
基于微信小程序的在线聊天功能实现:WebSocket通信实战
前端·后端·websocket·网络协议·微信小程序·小程序·notepad++
Warren9810 小时前
Java面试八股Spring篇(4500字)
java·开发语言·spring boot·后端·spring·面试
背帆10 小时前
go的interface接口底层实现
开发语言·后端·golang
IT成长史11 小时前
deepseek梳理java高级开发工程师springboot面试题2
java·spring boot·后端
qq_2663487311 小时前
springboot AOP中,通过解析SpEL 表达式动态获取参数值
java·spring boot·后端
敲代码的 蜡笔小新11 小时前
【行为型之命令模式】游戏开发实战——Unity可撤销系统与高级输入管理的架构秘钥
unity·设计模式·架构·命令模式
m0_5557629011 小时前
D-Pointer(Pimpl)设计模式(指向实现的指针)
设计模式
小Mie不吃饭11 小时前
【23种设计模式】分类结构有哪些?
java·设计模式·设计规范
bing_15812 小时前
MQTT 在Spring Boot 中的使用
java·spring boot·后端·mqtt
阑梦清川15 小时前
关于Go语言的开发环境的搭建
开发语言·后端·golang