記憶化源於 Lisp、Python、Perl 等語言,是一種設計模式:緩存函數的計算結果,相同輸入直接返回緩存值,避免重複計算。對於計算密集型方法(如計算圓周率二進制位),能顯著提升性能,且屬可復用橫切關注點。
在 PiBinaryDigitsCalculator 內部加 HashMap 緩存。缺點:業務邏輯與緩存邏輯耦合,難測試、難移除、不可復用。
定義介面 BinaryDigitsCalculator,實現類 PiBinaryDigitsCalculator 專注算法,包裝類 CachingBinaryDigitsCalculator 持有被包裝實例並添加緩存。優點:職責分離,易測試(可注入 Mock 實現驗證緩存行為),可熱插拔。
JDK 1.3+ 引入 java.lang.reflect.Proxy,運行時動態生成實現指定介面的代理類。我們編寫通用 Memoizer 實現 InvocationHandler,一次編寫、處處復用:
BinaryDigitsCalculator calculator = (BinaryDigitsCalculator) Memoizer.memoize(new PiBinaryDigitsCalculator());
memoize(Object target):創建代理實例,代理目標物件實現的所有介面。invoke(proxy, method, args):攔截方法調用。
void 方法直接執行,不緩存。void 方法:以 (Method, 參數列表) 為 Key 查詢緩存(Map<Method, Map<List<Object>, Object>>),命中則返回,否則反射調用目標方法、存入緩存並返回。被記憶化的方法必須滿足:
void 方法自動排除)。反射調用開銷:JDK 1.4 約為直接調用 1/2(JDK 1.3 為 1/40)。僅當緩存命中帶來的計算節省遠超反射開銷時才值得使用。建議:基準測試對比,輕量級方法不適用。
支援 LRU、大小限制等策略:
Memoizer.memoize(target, new LruCacheFactory(1000));
Memoizer 以 ~100 行代碼實現了通用、可配置、線程安全的記憶化組件,是 Java 開發者工具箱中的利器。正確識別適用場景,度量收益,謹慎使用反射。