文章目錄
- 簡介
- 問題
- 解決方案
- 代碼
- 核心設計要點
- 總結
簡介
代理是一種結構型設計模式,讓你能夠提供對象的替代品或其占位符。代理控制著對于原對象的訪問,并允許在把請求提交給對象前后進行一些處理。
問題
為什么要控制對于某個對象的訪問呢?舉個例子:有一個消耗大量系統資源的巨型對象,你只是偶爾需要使用它。
如上圖,數據庫連接的初始化和查詢可能會非常緩慢,我們可以先實現延遲初始化,只在實際有需要時再創建這個對象。這樣對象的所有客戶端都要執行延遲初始代碼。這很可能會帶來很多重復代碼。在理想情況下,我們希望把延遲初始化的代碼直接放入這個對象的類里,但這并不一定能實現, 因為這個類可能是第三方封閉庫的一部分。
解決方案
代理模式建議 新建一個和原來的服務對象接口相同的代理類,然后把代理對象傳遞給所有原始對象客戶端。代理類接收到客戶端請求后會創建實際的服務對象,并把所有工作委派給它。
如上圖,代理把自己偽裝成數據庫對象,可以在客戶端或實際數據庫對象不知情的情況下處理延遲初始化和緩存查詢結果的工作。
這有什么好處呢?如果需要在類的主要業務邏輯前后執行一些工作,你不需要修改原始類就能完成這個工作。而且由于代理實現的接口與原類相同,因此你可以在任何一個使用實際服務對象的客戶端中使用它。
代碼
// 服務接口
interface Database {String executeQuery(String sql);
}// 真實服務對象
class RealDatabase implements Database {public RealDatabase() {initializeConnection(); // 耗時的連接初始化}private void initializeConnection() {System.out.println("正在建立數據庫連接...");}@Overridepublic String executeQuery(String sql) {System.out.println("執行真實查詢: " + sql);return "結果數據"; // 示例返回值}
}// 代理類(延遲加載和緩存)
class DatabaseProxy implements Database {private RealDatabase realDatabase; private Map<String, String> cache = new HashMap<>(); // 查詢結果緩存@Overridepublic String executeQuery(String sql) {// 延遲初始化(虛擬代理模式)if (realDatabase == null) {realDatabase = new RealDatabase(); }// 結果緩存邏輯(緩存代理)if (cache.containsKey(sql)) {System.out.println("[Proxy] 返回緩存結果: " + sql);return cache.get(sql);}String result = realDatabase.executeQuery(sql);cache.put(sql, result);return result;}
}// 客戶端交互
class Application {public static void main(String[] args) {Database proxy = new DatabaseProxy();// 第一次查詢觸發真實連接System.out.println(proxy.executeQuery("SELECT * FROM users"));// 重復查詢使用緩存System.out.println(proxy.executeQuery("SELECT * FROM users"));// 新查詢繼續代理System.out.println(proxy.executeQuery("SELECT COUNT(*) FROM products"));}
}
核心設計要點
- 代理類與現實服務實現相同接口
- 第一次查詢延遲初始化真實連接(虛擬代理模式)
- 哈希表緩存重復查詢結果(緩存優化)
- 客戶端代碼不需感知代理存在(透明訪問)
總結
- 服務接口(Ser-vice Inter-face):聲明接口,代理必須遵循這個接口才能偽裝成服務對象。
- 服務(Ser-vice)類提供了一些實用的業務邏輯。
- 代理(Proxy)類包含一個指向服務對象的引用成員變量。代理完成他的任務之后,(比如延遲初始化、記錄日志、訪問控制和緩存等),會把請求傳遞給服務對象。通常情況下,代理會對這個服務對象的整個生命周期進行管理。
- 客戶端(Client)能通過同一接口跟服務或代理進行交互,所以你可在一切需要服務對象的代碼中使用代理。