代理模式-訪問物件的代理而非其本身
阿新 • • 發佈:2021-01-11
> **公號:碼農充電站pro**
> **主頁:**
本篇來介紹**代理模式**(*Proxy Design Pattern*),通過代理模式可以控制和管理物件的訪問。
### 1,代理模式
**代理模式為物件提供一個代理,來控制對該物件的訪問**。代理代表了原始物件,而不是真實的物件本身。
代理模式在不改變原始類程式碼的情況下,通過引入**代理類**來給原始類附加功能。
代理模式的類圖如下:
![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20210105182656328.png?)
**RealSubject** 是原始類,**Proxy** 代理類,它們都實現了 **Subject** 介面,這使得代理類可以取代原始類。
原始類通常在代理類的後面,客戶端不會直接訪問原始類,而是隻訪問代理類。代理類在收到客戶端的請求之後,會替客戶端去訪問原始類,然後將結果返回給客戶端。
![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20210105184208959.png)
代理類中儲存了原始類物件的引用,代理類的實際操作還是通過呼叫原始類來完成,但代理類除了完成原始類的基本功能之外,還可以新增一些其它必要的功能。
> 這看起來,代理模式很像裝飾器模式,但它們的設計意圖是不一樣的。代理模式的目的是為了控制和管理原始物件的訪問;而裝飾器模式是為了增強原有物件的能力,而且往往一個物件會被一個或多個裝飾器多次包裝。
### 2,代理模式示例
下面通過一個簡單的例子,來看下如何使用代理模式。
假如我們現在有一個類 **Server**:
```java
class Server {
public void handleRequest() {
// 處理過程
System.out.println("handle client request.");
}
}
```
**Server** 類中有一個 `handleRequest` 方法,用於處理客戶端請求。
現在我們想統計處理客戶端請求需要的時間,最簡單直接的做法是在 `handleRequest` 方法中新增計算時間的程式碼,如下:
```java
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**,讓代理類和原始類都繼承該介面。
```java
interface ServerInterface {
void handleRequest();
}
```
原始類實現 **ServerInterface** 介面,**Server** 只需專注業務處理:
```java
class Server implements ServerInterface {
public void handleRequest() {
// 處理過程
System.out.println("handle client request.");
}
}
```
下面建立代理類,負責統計時間:
```java
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** 帶來不必要的麻煩。
***使用繼承的方式來實現代理***
上面這種代理的實現方式,使用的是組合的方式,也就是代理類中儲存了一個原始類物件的引用。
如果在實際的專案中,原始類來自第三方庫,這樣就不能讓原始類實現一個介面,因為我們不能修改第三方庫。
此時,為了使用代理模式,可以繼承的方式,也就是讓代理類繼承原始類,如下:
```java
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);
}
}
```
此時,代理模式的類圖就變成了下面這樣:
![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20210105193818975.png)
### 3,代理模式的應用
代理模式有很多的應用場景,下面來看一些常用的。
***遠端代理***
**遠端代理**用於控制訪問遠端物件,客戶端與原始類在不同的地址空間中,遠端代理通過網路來為客戶端提供服務。
遠端代理的架構圖如下:
![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20210105221715602.png?)
上圖中的客戶輔助物件就是代理,客戶端將請求發給代理,代理將請求通過網路轉發給服務輔助物件。
服務輔助物件接收代理的請求後,再將請求轉給真實的服務物件,服務物件處理完請求後,再將處理結果一步步的傳給客戶端。
在 **Java** 中可以通過 **RMI** 來構建遠端代理服務。
***虛擬代理***
**虛擬代理**用於控制訪問**建立開銷大的資源**,它作為建立開銷大的物件的代表。
在虛擬代理中,只有我們真正需要一個物件的時候,才會建立它。
在物件建立完成之前,由虛擬代理來處理客戶端的訪問;在物件建立之後,虛擬代理將客戶端的請求委託給真實物件處理。
***保護代理***
**保護代理**基於訪問許可權來控制對資源的訪問,**保護代理**可以不讓客戶端訪問某些資源。
***防火牆代理***
防火牆代理用於控制網路資源的訪問,使資源免於“壞客戶”的攻擊。
***智慧引用代理***
當資源被引用時,計算資源被引用的次數。
***快取代理***
為開銷大的資源提供暫存服務,以減少計算和網路延遲。
### 4,動態代理
代理模式有一個缺點,就是需要在代理類中實現原始類的所有方法,而且如果原始類很多的話,就需要建立很多的代理類,從而導致專案中類的數量倍增。如果代理類的功能相近的話,還會導致許多重複程式碼。
為了解決這個問題,可以使用**動態代理**。動態代理不需要事先為每個原始類編寫代理類,而是在執行時,**動態的為原始類建立代理類**,然後用代理類來替換原始類。
在 **Java** 中可以使用 `java.lang.reflect` 包中的 **Proxy** 類和 **InvocationHandler** 介面來實現動態代理,其中用到了**Java 反射**的原理。
### 5,總結
代理模式提供了一個原有服務的代理,這樣使得客戶不直接訪問原有服務,而是通過代理間接訪問原服務,達到了控制原有服務訪問的目的。
代理模式可以用組合與繼承兩種方式來實現。代理模式有時候會導致代理類過多和程式碼重複的問題,這個問題可以用動態代理來解決。
有時候代理模式看起來像是[裝飾者模式](https://www.cnblogs.com/codeshell/p/14210116.html),但它們的設計意圖是不一樣的。
代理模式的應用場景有很多,比如遠端代理,虛擬代理,保護代理等。
(本節完。)
---
**推薦閱讀:**
[介面卡模式-讓不相容的介面得以適配](https://www.cnblogs.com/codeshell/p/14228610.html)
[外觀模式-簡化子系統的複雜性](https://www.cnblogs.com/codeshell/p/14234062.html)
[模板方法模式-封裝一套演算法流程](https://www.cnblogs.com/codeshell/p/14239615.html)
[迭代器模式-統一集合的遍歷方式](https://www.cnblogs.com/codeshell/p/14244944.html)
[狀態模式-將狀態和行為封裝成物件](https://www.cnblogs.com/codeshell/p/14250233.html)
---
*歡迎關注作者公眾號,獲取更多技術乾貨。*
![碼農充電站pro](https://img-blog.csdnimg.cn/20200505082843773.png?#pic