應用程式大量使用快取時,常見模式為:
這段邏輯重複出現在許多方法中,違反 DRY (Don't Repeat Yourself) 原則,且當資料源存取涉及多類別協作、交易控制時,程式碼更顯冗長。
定義泛型抽象類別 `CacheReader
public abstract class CacheReader
{
/// 從快取讀取,成功回傳 true 並輸出資料
public abstract bool GetFromCache(out T data);
/// 從資料源讀取資料
public abstract T ReadFromSource();
/// 將資料寫入快取
public abstract void SetToCache(T data);
/// 統一快取讀取流程
public T Read()
{
if (GetFromCache(out T data)) return data;
data = ReadFromSource();
SetToCache(data);
return data;
}
}
private class GetFriendCacheReader : CacheReader>
{
private readonly int _userId;
private readonly string _cacheKey;
private readonly CacheManager _cacheManager;
public GetFriendCacheReader(int userId, CacheManager cacheManager)
{
_userId = userId;
_cacheKey = $"friends_of_user_{userId}";
_cacheManager = cacheManager;
}
public override bool GetFromCache(out List data)
{
var obj = _cacheManager.Get(_cacheKey);
if (obj == null) { data = null; return false; }
data = (List)obj;
return true;
}
public override List ReadFromSource()
=> new UserService().GetFriends(_userId);
public override void SetToCache(List data)
=> _cacheManager.Set(_cacheKey, data);
}
var reader = new GetFriendCacheReader(userId, cacheManager);
List friends = reader.Read();
若不想為每個查詢建立獨立類別,可設計接受委派的輔助方法:
public static class CacheHelper
{
public static T GetOrAdd(string key, Func loader, TimeSpan? expiry = null)
{
if (CacheManager.Instance.TryGet(key, out T cached)) return cached;
T value = loader();
CacheManager.Instance.Set(key, value, expiry ?? TimeSpan.FromMinutes(30));
return value;
}
}
// 簡潔的一行程式碼完成快取邏輯
var friends = CacheHelper.GetOrAdd(
$"friends_of_user_{userId}",
() => new UserService().GetFriends(userId),
TimeSpan.FromHours(1)
);
| 維度 | 基類模式 | Lambda 輔助方法 |
|---|---|---|
| 程式碼結構 | 明確分離關注點,適合複雜邏輯 | 極度精簡,適合簡單查詢 |
| 可測試性 | 易於 Mock 抽象方法進行單元測試 | 需 Mock 靜態方法或改用介面注入 |
| 擴充性 | 可加入前後攔截、日誌、計量 | 需修改輔助方法簽名 |
| 適用場景 | 資料源存取複雜、需交易控制、多步驟組合 | 單一查詢、快取鍵值固定、邏輯單純 |
針對重複的快取讀取邏輯,採用「範本方法模式」封裝於基類,或使用「Lambda 輔助方法」簡化呼叫端,皆能有效落實 DRY 原則。專案中可依複雜度混合採用:核心業務查詢用基類確保可維護性,邊緣簡單查詢用 Lambda 提升開發速度。