公號:碼農充電站pro
主頁:https://codeshellme.github.iohtml
本篇來介紹代理模式(Proxy Design Pattern),經過代理模式能夠控制和管理對象的訪問。java
代理模式爲對象提供一個代理,來控制對該對象的訪問。代理表明了原始對象,而不是真實的對象自己。git
代理模式在不改變原始類代碼的狀況下,經過引入代理類來給原始類附加功能。github
代理模式的類圖以下:算法
RealSubject 是原始類,Proxy 代理類,它們都實現了 Subject 接口,這使得代理類能夠取代原始類。shell
原始類一般在代理類的後面,客戶端不會直接訪問原始類,而是隻訪問代理類。代理類在收到客戶端的請求以後,會替客戶端去訪問原始類,而後將結果返回給客戶端。緩存
代理類中保存了原始類對象的引用,代理類的實際操做仍是經過調用原始類來完成,但代理類除了完成原始類的基本功能以外,還能夠添加一些其它必要的功能。網絡
這看起來,代理模式很像裝飾器模式,但它們的設計意圖是不同的。代理模式的目的是爲了控制和管理原始對象的訪問;而裝飾器模式是爲了加強原有對象的能力,並且每每一個對象會被一個或多個裝飾器屢次包裝。架構
下面經過一個簡單的例子,來看下如何使用代理模式。this
假如咱們如今有一個類 Server:
class Server { public void handleRequest() { // 處理過程 System.out.println("handle client request."); } }
Server 類中有一個 handleRequest
方法,用於處理客戶端請求。
如今咱們想統計處理客戶端請求須要的時間,最簡單直接的作法是在 handleRequest
方法中添加計算時間的代碼,以下:
public void handleRequest() { long startTime = System.currentTimeMillis(); // 處理過程 System.out.println("handle client request."); long endTime = System.currentTimeMillis(); long reqTime = endTime - startTime; System.out.println(reqTime); }
這樣作的缺點是,在正常的業務處理流程中,添加了一些無關代碼,使得統計代碼與業務代碼耦合在一塊兒。
這種狀況,就可使用代理模式來處理,而無需改動原有代碼。
首選建立一個接口 ServerInterface,讓代理類和原始類都繼承該接口。
interface ServerInterface { void handleRequest(); }
原始類實現 ServerInterface 接口,Server 只需專一業務處理:
class Server implements ServerInterface { public void handleRequest() { // 處理過程 System.out.println("handle client request."); } }
下面建立代理類,負責統計時間:
class ServerProxy implements ServerInterface { private Server server; public ServerProxy(Server server) { this.server = server; } public void handleRequest() { long startTime = System.currentTimeMillis(); // 調用原始類 server.handleRequest(); long endTime = System.currentTimeMillis(); long reqTime = endTime - startTime; System.out.println(reqTime); } }
ServerProxy 中保存了 Server 對象的引用,會在必要的時候調用原始類,從而完成原始類的功能。
以後,客戶端只須要與代理交互,而不須要跟原始類交互。
能夠看到,這種實現方式並無直接修改 Server,而是建立了 Server 的代理,避免給 Server 帶來沒必要要的麻煩。
使用繼承的方式來實現代理
上面這種代理的實現方式,使用的是組合的方式,也就是代理類中保存了一個原始類對象的引用。
若是在實際的項目中,原始類來自第三方庫,這樣就不能讓原始類實現一個接口,由於咱們不能修改第三方庫。
此時,爲了使用代理模式,能夠繼承的方式,也就是讓代理類繼承原始類,以下:
class ServerProxy extends Server { public void handleRequest() { long startTime = System.currentTimeMillis(); // 調用原始類 super.handleRequest(); long endTime = System.currentTimeMillis(); long reqTime = endTime - startTime; System.out.println(reqTime); } }
此時,代理模式的類圖就變成了下面這樣:
代理模式有不少的應用場景,下面來看一些經常使用的。
遠程代理
遠程代理用於控制訪問遠程對象,客戶端與原始類在不一樣的地址空間中,遠程代理經過網絡來爲客戶端提供服務。
遠程代理的架構圖以下:
上圖中的客戶輔助對象就是代理,客戶端將請求發給代理,代理將請求經過網絡轉發給服務輔助對象。
服務輔助對象接收代理的請求後,再將請求轉給真實的服務對象,服務對象處理完請求後,再將處理結果一步步的傳給客戶端。
在 Java 中能夠經過 RMI 來構建遠程代理服務。
虛擬代理
虛擬代理用於控制訪問建立開銷大的資源,它做爲建立開銷大的對象的表明。
在虛擬代理中,只有咱們真正須要一個對象的時候,纔會建立它。
在對象建立完成以前,由虛擬代理來處理客戶端的訪問;在對象建立以後,虛擬代理將客戶端的請求委託給真實對象處理。
保護代理
保護代理基於訪問權限來控制對資源的訪問,保護代理能夠不讓客戶端訪問某些資源。
防火牆代理
防火牆代理用於控制網絡資源的訪問,使資源免於「壞客戶」的攻擊。
智能引用代理
當資源被引用時,計算資源被引用的次數。
緩存代理
爲開銷大的資源提供暫存服務,以減小計算和網絡延遲。
代理模式有一個缺點,就是須要在代理類中實現原始類的全部方法,並且若是原始類不少的話,就須要建立不少的代理類,從而致使項目中類的數量倍增。若是代理類的功能相近的話,還會致使許多重複代碼。
爲了解決這個問題,可使用動態代理。動態代理不須要事先爲每一個原始類編寫代理類,而是在運行時,動態的爲原始類建立代理類,而後用代理類來替換原始類。
在 Java 中可使用 java.lang.reflect
包中的 Proxy 類和 InvocationHandler 接口來實現動態代理,其中用到了Java 反射的原理。
代理模式提供了一個原有服務的代理,這樣使得客戶不直接訪問原有服務,而是經過代理間接訪問原服務,達到了控制原有服務訪問的目的。
代理模式能夠用組合與繼承兩種方式來實現。代理模式有時候會致使代理類過多和代碼重複的問題,這個問題能夠用動態代理來解決。
有時候代理模式看起來像是裝飾者模式,但它們的設計意圖是不同的。
代理模式的應用場景有不少,好比遠程代理,虛擬代理,保護代理等。
(本節完。)
推薦閱讀:
歡迎關注做者公衆號,獲取更多技術乾貨。