装饰者模式?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 接口的实现类。

相关推荐
zyxzyx66623 分钟前
Canal 解析与 Spring Boot 整合实战
java·spring boot·后端
Studying_swz1 小时前
Spring WebFlux之流式输出
java·后端·spring
苏墨瀚1 小时前
C#语言的响应式设计
开发语言·后端·golang
苏墨瀚3 小时前
SQL语言的散点图
开发语言·后端·golang
飞翔中文网4 小时前
Java设计模式之装饰器模式
java·设计模式
大怪v7 小时前
前端佬们,装起来!给设计模式【祛魅】
前端·javascript·设计模式
一只韩非子8 小时前
一句话告诉你什么叫编程语言自举!
前端·javascript·后端
沈二到不行8 小时前
多头注意力&位置编码:完型填空任务
人工智能·后端·deepseek
追逐时光者8 小时前
C# 中比较实用的关键字,基础高频面试题!
后端·c#·.net
GoGeekBaird9 小时前
一文搞懂:Anthropic发布MCP重要更新,告别长连接
后端·操作系统