找回密码
 立即注册
首页 业界区 业界 开窍了!如何为缓存工具类(CacheUtil中的static方法) ...

开窍了!如何为缓存工具类(CacheUtil中的static方法)定义interface(下)

仟仞 3 天前
两个不同策略的缓存工具类

在我们系统的基建包里,有一个基于redis的get/set等基础api封装的 CacheUtil。
CacheUtil 主要有下面2个静态方法:
  1. import java.util.function.Supplier;
  2. public class CacheUtil {
  3.     /**
  4.      * 获取缓存。如果没有,则设置
  5.      */
  6.     public static <T> T getCache(String key, long seconds, Supplier<T> supplier) {
  7.         return getCache(key, seconds, false, supplier);
  8.     }
  9.     /**
  10.      * 获取缓存。如果没有,则设置
  11.      */
  12.     public static <T> T getCache(String key, long seconds, boolean cacheNull, Supplier<T> supplier) {
  13.         Object obj = redisUtil.get(key); // 这里的RedisUtil类封装了 redis 的get/set等基础操作
  14.         if (null == obj) {
  15.             T value = supplier.get();
  16.             ......
  17.             redisUtil.set(key, value, seconds);
  18.             return value;
  19.         } else {
  20.                 ......
  21.             return (T) obj;
  22.         }
  23.     }
  24. }
复制代码
随着后续系统迭代过程中,我增加了一个基于本地缓存框架 hutool-cache 的 LFUCache、TimedCache 来实现的 LocalCacheUtil。
与 CacheUtil 一样的是,LocalCacheUtil 中也主要有下面2个静态方法:
  1. import java.util.function.Supplier;
  2. public class CacheUtil {
  3.     /**
  4.      * 获取缓存。如果没有,则设置
  5.      */
  6.     public static <T> T getCache(String key, long seconds, Supplier<T> supplier) {
  7.         return getCache(key, seconds, false, supplier);
  8.     }
  9.     /**
  10.      * 获取缓存。如果没有,则设置
  11.      */
  12.     public static <T> T getCache(String key, long seconds, boolean allowCacheNullOrEmpty, Supplier<T> supplier) {
  13.         return getCache(timedCache, key, seconds, allowCacheNullOrEmpty, supplier);
  14.     }
  15.     private static <T> T getCache(Cache<String, Object> myCache, String key, Long seconds, boolean allowCacheNullOrEmpty, Supplier<T> supplier) {
  16.         Object cachedValue = myCache.get(key, false);
  17.         if (cachedValue != null) {
  18.             return (T) cachedValue;
  19.         }
  20.         // 允许缓存null值的情况下,如果存在缓存,则直接返回
  21.         if (allowCacheNullOrEmpty && myCache.containsKey(key)) {
  22.             return (T) myCache.get(key, false);
  23.         }
  24.         ......
  25.         T result = supplier.get();
  26.         if (seconds == null) {
  27.             myCache.put(key, result);
  28.         } else {
  29.             myCache.put(key, result, TimeUnit.SECONDS.toMillis(seconds));
  30.         }
  31.         return result;
  32.     }
  33. }
复制代码
如何为两个缓存工具类抽取公共能力?

翻阅 git 提交记录,我发现 CacheUtil 是2020-09 创建的,LocalCacheUtil 是 2022-12 创建的。
虽然两年多过去了,但这其中有一个困扰着我的程序设计问题并没有被遗忘。
这个程序设计问题是, CacheUtil 与 LocalCacheUtil 的职责是相同的,两者都是用来缓存数据。那么,如果能够为两者抽象出来一个缓存数据的 interface,该多香啊!
可是, getCache 方法是 static 静态方法。我们知道,静态方法是无法实现接口的
我总不能把 getCache 方法改为非静态方法吧?
我不能。倒不是因为需要改所有的调用代码,而是在程序设计原则中,工具类的设计理念通常是为了提供一组相关的实用方法,这些方法不依赖于类的实例状态,而是专注于执行特定的功能。代码实现中,我们通常将工具类的方法定义为 static 或者通过其他方式(如私有化构造函数)防止类在外部被实例化
那么,我没有办法了!
我曾经在遥远的2017年听过一个架构师讲过类似场景的解决方案,可惜的是,忘却了,脑子里只留下“讲解过”这三个字了。
直到最近,我才想到方案。
知识就是力量,但更重要的是,运用知识的能力

设计模式里的单例模式是良药。是的,饿汉式单例模式(eager singleton pattern)。
以下是使用饿汉式单例模式为 CacheUtil 和 LocalCacheUtil 抽象出接口并进行代码改造的实现。
首先,定义一个缓存接口 CacheService,定义了两个 getCache 方法,这两个方法是缓存工具类的核心操作,不同的缓存实现类需要实现这些方法。
  1. import java.util.function.Supplier;
  2. // 定义缓存服务接口
  3. public interface CacheService {
  4.     /**
  5.      * 获取缓存。如果没有,则设置
  6.      *
  7.      * @param key
  8.      * @param seconds
  9.      * @param supplier 缓存数据提供者
  10.      * @param <T>
  11.      * @return
  12.      */
  13.     <T> T getCache(String key, long seconds, Supplier<T> supplier);
  14.     /**
  15.      * 获取缓存。如果没有,则设置
  16.      *
  17.      * @param key
  18.      * @param seconds
  19.      * @param cacheNull 是否缓存null
  20.      * @param supplier 缓存数据提供者
  21.      * @param <T>
  22.      * @return
  23.      */
  24.     <T> T getCache(String key, long seconds, boolean cacheNull, Supplier<T> supplier);
  25. }
复制代码
接着,将原来的 CacheUtil 类改造为实现 CacheService 接口的单例类,提供基于 Redis 的缓存操作。
  1. // Redis缓存实现类,使用饿汉式单例模式
  2. public class RedisCacheUtil implements CacheService {
  3.     // 饿汉式单例,在类加载时就创建实例
  4.     public static final RedisCacheUtil INSTANCE = new RedisCacheUtil();
  5.     // 私有化构造函数,防止外部实例化
  6.     private RedisCacheUtil() {}
  7.     @Override
  8.     public <T> T getCache(String key, long seconds, Supplier<T> supplier) {
  9.         return getCache(key, seconds, false, supplier);
  10.     }
  11.     @Override
  12.     public <T> T getCache(String key, long seconds, boolean cacheNull, Supplier<T> supplier) {
  13.         ... ...
  14.     }
  15. }
复制代码
然后,将 LocalCacheUtil 类改造为实现 CacheService 接口的单例类,提供基于本地缓存(hutool-cache)的缓存操作。
  1. // 本地缓存实现类,使用饿汉式单例模式
  2. public class LocalCacheUtil implements CacheService {
  3.     // 饿汉式单例,在类加载时就创建实例
  4.     public static final LocalCacheUtil INSTANCE = new LocalCacheUtil();
  5.     // 私有化构造函数,防止外部实例化
  6.     private LocalCacheUtil() {}
  7.     @Override
  8.     public <T> T getCache(String key, long seconds, Supplier<T> supplier) {
  9.         return getCache(key, seconds, false, supplier);
  10.     }
  11.     @Override
  12.     public <T> T getCache(String key, long seconds, boolean allowCacheNullOrEmpty, Supplier<T> supplier) {
  13.         return getCache(timedCache, key, seconds, allowCacheNullOrEmpty, supplier);
  14.     }
  15.     private <T> T getCache(Cache<String, Object> myCache, String key, Long seconds, boolean allowCacheNullOrEmpty, Supplier<T> supplier) {
  16.         ... ...
  17.     }
  18. }
复制代码
通过这种引入饿汉式单例模式的方式,我们成功地为 CacheUtil 和 LocalCacheUtil 两个工具类抽象出了接口。规范了缓存工具类的操作能力。(PS:比较熟悉java8的同学,一眼能看出来,CacheService里,第一个 getCache 可以用 default 来修饰,同样,两个实现类不需要override这个 getCache。这会更香!————当然,这不在本文议题内

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册