动态

详情 返回 返回

開竅了!如何為緩存工具類(CacheUtil中的static方法)定義interface(下) - 动态 详情

兩個不同策略的緩存工具類

在我們系統的基建包裏,有一個基於redis的get/set等基礎api封裝的 CacheUtil
CacheUtil 主要有下面2個靜態方法:

public class CacheUtil {
    /**
     * 獲取緩存。如果沒有,則設置
     */
    public static <T> T getCache(String key, long seconds, Supplier<T> supplier) {
        return getCache(key, seconds, false, supplier);
    }

    /**
     * 獲取緩存。如果沒有,則設置
     */
    public static <T> T getCache(String key, long seconds, boolean cacheNull, Supplier<T> supplier) {
        Object obj = redisUtil.get(key); // 這裏的RedisUtil類封裝了 redis 的get/set等基礎操作
        if (null == obj) {
            T value = supplier.get();
            ......
            redisUtil.set(key, value, seconds);
            return value;
        } else {
        	......
            return (T) obj;
        }
    }
}

隨着後續系統迭代過程中,我增加了一個基於本地緩存框架 hutool-cache 的 LFUCache、TimedCache 來實現的 LocalCacheUtil
CacheUtil 一樣的是,LocalCacheUtil 中也主要有下面2個靜態方法:

public class LocalCacheUtil {
    /**
     * 獲取緩存。如果沒有,則設置
     */
    public static <T> T getCache(String key, long seconds, Supplier<T> supplier) {
        return getCache(key, seconds, false, supplier);
    }

    /**
     * 獲取緩存。如果沒有,則設置
     */
    public static <T> T getCache(String key, long seconds, boolean allowCacheNullOrEmpty, Supplier<T> supplier) {
        return getCache(timedCache, key, seconds, allowCacheNullOrEmpty, supplier);
    }

    private static <T> T getCache(Cache<String, Object> myCache, String key, Long seconds, boolean allowCacheNullOrEmpty, Supplier<T> supplier) {
        Object cachedValue = myCache.get(key, false);
        if (cachedValue != null) {
            return (T) cachedValue;
        }
        // 允許緩存null值的情況下,如果存在緩存,則直接返回
        if (allowCacheNullOrEmpty && myCache.containsKey(key)) {
            return (T) myCache.get(key, false);
        }

        ......
        T result = supplier.get();
        if (seconds == null) {
            myCache.put(key, result);
        } else {
            myCache.put(key, result, TimeUnit.SECONDS.toMillis(seconds));
        }
        return result;
    }
}

如何為兩個緩存工具類抽取公共能力?

翻閲代碼倉庫的commit記錄,我發現 CacheUtil 是2020-09 創建的,LocalCacheUtil 是 2022-12 創建的。
雖然兩年多過去了,但這其中有一個困擾着我的程序設計問題並沒有被遺忘。

這個程序設計問題是, CacheUtilLocalCacheUtil 的職責是相同的,兩者都是用來緩存數據。那麼,如果能夠為兩者抽象出來一個緩存數據的 interface,該多香啊!

可是, getCache 方法是 static 靜態方法。我們知道,靜態方法是無法實現接口的

我總不能把 getCache 方法改為非靜態方法吧?

我不能。倒不是因為需要改所有的調用代碼,而是在程序設計原則中,工具類的設計理念通常是為了提供一組相關的實用方法,這些方法不依賴於類的實例狀態,而是專注於執行特定的功能。代碼實現中,我們通常將工具類的方法定義為 static 或者通過其他方式(如私有化構造函數)防止類在外部被實例化

那麼,我沒有辦法了!

我曾經在遙遠的2017年聽過一個架構師講過類似場景的解決方案,可惜的是,忘卻了,腦子裏只留下“講解過”這三個字了。

直到最近,我才想到方案。

知識就是力量,但更重要的是,運用知識的能力

設計模式裏的單例模式是良藥。是的,餓漢式單例模式(eager singleton pattern)。

以下是使用餓漢式單例模式為 CacheUtilLocalCacheUtil 抽象出接口並進行代碼改造的實現。

首先,定義一個緩存接口 CacheService,包含兩個 getCache 方法,這兩個方法是緩存工具類的核心操作,不同的緩存實現類需要實現這些方法。

// 定義緩存服務接口
public interface CacheService {
    /**
     * 獲取緩存。如果沒有,則設置
     *
     * @param key
     * @param seconds
     * @param supplier 緩存數據提供者
     * @param <T>
     * @return
     */
    <T> T getCache(String key, long seconds, Supplier<T> supplier);

    /**
     * 獲取緩存。如果沒有,則設置
     *
     * @param key
     * @param seconds
     * @param cacheNull 是否緩存null
     * @param supplier 緩存數據提供者
     * @param <T>
     * @return
     */
    <T> T getCache(String key, long seconds, boolean cacheNull, Supplier<T> supplier);
}

接着,讓原來的 CacheUtil 類改造為實現 CacheService 接口的單例類,提供基於 Redis 的緩存操作;讓 LocalCacheUtil 類改造為實現 CacheService 接口的單例類,提供基於本地緩存(hutool-cache)的緩存操作。

改造後的CacheUtil 類(為了易讀,我重命名成了RedisCacheUtil):

// Redis緩存實現類,使用餓漢式單例模式
public class RedisCacheUtil implements CacheService {
    // 餓漢式單例,在類加載時就創建實例
    public static final RedisCacheUtil INSTANCE = new RedisCacheUtil();

    // 私有化構造函數,防止外部實例化
    private RedisCacheUtil() {}

    @Override
    public <T> T getCache(String key, long seconds, Supplier<T> supplier) {
        ... ...
    }

    @Override
    public <T> T getCache(String key, long seconds, boolean cacheNull, Supplier<T> supplier) {
        ... ...
    }
}

改造後的LocalCacheUtil 類:

// 本地緩存實現類,使用餓漢式單例模式
public class LocalCacheUtil implements CacheService {
    // 餓漢式單例,在類加載時就創建實例
    public static final LocalCacheUtil INSTANCE = new LocalCacheUtil();

    // 私有化構造函數,防止外部實例化
    private LocalCacheUtil() {}

    @Override
    public <T> T getCache(String key, long seconds, Supplier<T> supplier) {
        ... ...
    }

    @Override
    public <T> T getCache(String key, long seconds, boolean allowCacheNullOrEmpty, Supplier<T> supplier) {
        ... ...
    }
}

通過這種引入餓漢式單例模式的方式,我們成功地為 CacheUtilLocalCacheUtil 兩個工具類抽象出了接口。規範了緩存工具類的操作能力。(PS:比較熟悉java8的同學,一眼能看出來,CacheService裏,第一個 getCache 可以用 default 來修飾,同樣,兩個實現類不需要override這個 getCache。這會更香!————當然,這不在本文議題內

user avatar tonnyking 头像 gtyan 头像
点赞 2 用户, 点赞了这篇动态!
点赞

Add a new 评论

Some HTML is okay.