【設計模式(12)】結構型模式之代理模式
個人學習筆記分享,當前能力有限,請勿貶低,菜鳥互學,大佬繞道
如有勘誤,歡迎指出和討論,本文後期也會進行修正和補充
前言
“中介”這個職業大家都知道,比如買房租房,我們一般是與中介進行交流、看房、議價等等,甚至我上一次租的房住了一年都沒見過房東,同理房東也沒見過我。。。
還有譬如買火車票並不需要去火車站,在攜程或者12306app上就可以購買;訂酒店通過美團或者攜程也能夠完成,並不需要本人去酒店;還有瀏覽那些“不存在”的網站的時候,我們需要“梯子“等等
以租房中介來說,一方面他是我的中介,代替我去跟房東談價格談房屋配置,談妥之前房東並不知道我是誰,相當於我的代理。
另一方面他也是房東的中介,代替房東跟我談價格談合同,談妥之前房東同樣不知道我是誰,相當於房東的代理。
而且在代理的過程中,並不一定會原話轉達給對方,大概率會做一些小的變動,以促成合同的完成。這是他的能力範圍內的事情,當然會怎麼做看個人咯。
說到底,之所以需要中介,是因為我們不想或者不能直接訪問目標物件。
代理模式的定義:由於某些原因需要給某物件提供一個代理以控制對該物件的訪問。
這時,訪問物件不適合或者不能直接引用目標物件,代理物件作為訪問物件和目標物件之間的中介。
1.介紹
使用目的:為其他物件提供一種代理以控制對這個物件的訪問
使用時機:需要在訪問一個類時新增一些控制
解決問題:由於某些原因客戶端不能或者不想要直接訪問目標物件
實現方法:新增中間層,用於實現與被代理類組合。客戶端訪問中間層,以間接訪問被代理類
應用例項:
- Windows系統的快捷方式
- 機票、車票、酒店等代售點
- 房屋中介、職務中介等
- spring中的aop(面向切面程式設計)
優點:
- 客戶端不能直接訪問目標物件,可以保護目標物件,提高安全性
- 客戶端與目標物件解耦,職責清晰,也提高了程式的擴充套件性
- 代理可以擴充套件目標物件的功能
缺點:
- 代理模式需要在系統中新增類,提高了系統體積
- 客戶端和目標物件中增加了代理物件,會一定程度上降低執行速度
- 實現代理模式需要額外的工作,有些代理模式的實現非常複雜
使用場景:按職責來劃分,通常有以下使用場景
說來話長,不多贅述
- 遠端代理
- 虛擬代理
- Copy-on-Write 代理
- 保護(Protect or Access)代理
- Cache代理
- 防火牆(Firewall)代理
- 同步化(Synchronization)代理
- 智慧引用(Smart Reference)代理
2.結構
代理模式的主要角色如下
-
抽象主題(Subject)類:通過介面或抽象類宣告真實主題和代理物件實現的業務方法。
-
真實主題(Real Subject)類:實現了抽象主題中的具體業務,是代理物件所代表的真實物件,是最終要引用的物件。
-
代理(Proxy)類:提供了與真實主題相同的介面,其內部含有對真實主題的引用,它可以訪問、控制或擴充套件真實主題的功能。
-
代理類和真實主題類都需要實現介面抽象主題類,以保證其對外介面一致
-
客戶端呼叫代理類中的介面,實際上是對真實主題類中方法加上前置後置處理後的方法,但仍然需要呼叫真實主題類的方法
-
代理類需要持有一個真實主題類物件,以呼叫其中的目標方法
3.實現步驟
-
定義抽象主題介面
interface Subject { void Request(); }
-
定義真實主體類,實現抽象主題介面
class RealSubject implements Subject { @Override public void Request() { System.out.println("訪問真實主題方法..."); } }
-
定義代理類,實現抽象主題介面,並持有一個真實主題物件
class Proxy implements Subject { private RealSubject realSubject; public Proxy() { this.realSubject = new RealSubject(); } @Override public void Request() { preRequest(); realSubject.Request(); postRequest(); } private void preRequest() { System.out.println("訪問真實主題之前的預處理。"); } private void postRequest() { System.out.println("訪問真實主題之後的後續處理。"); } }
需要持有一個真實主題物件,構造這個物件的時間可以按照業務處理,一般有下面3種情況
- 寫在代理類建構函式中,構造代理類的同時構造真實主題物件
- 寫在介面中,呼叫介面時檢查是否已構造,沒有則此時構造
- 在客戶端中構造真實主題物件,通過代理類的建構函式傳入代理類
完整程式碼
package com.company.test.proxy;
//抽象主題
interface Subject {
void Request();
}
//真實主題
class RealSubject implements Subject {
@Override
public void Request() {
System.out.println("訪問真實主題方法...");
}
}
//代理
class Proxy implements Subject {
private RealSubject realSubject;
public Proxy() {
this.realSubject = new RealSubject();
}
@Override
public void Request() {
preRequest();
realSubject.Request();
postRequest();
}
private void preRequest() {
System.out.println("訪問真實主題之前的預處理。");
}
private void postRequest() {
System.out.println("訪問真實主題之後的後續處理。");
}
}
public class ProxyTest {
public static void main(String[] args) {
Proxy proxy = new Proxy();
proxy.Request();
}
}
執行結果
4.正向代理與反向代理
都是代理,一個正一個反,那麼到底是是啥反了?答案是代理的物件反了
通常說三個角色,客戶端—代理—服務端
-
正向代理是代理客戶端,隱藏客戶端的真實身份
如VPN,VPN伺服器會代替我們去訪問目標網站。對於目標網站而言,互動的物件是代理,他並不知道代理的背後是誰,甚至不知道是不是代理。
目的正是讓服務端不知道是誰在訪問。
-
反向代理是代理服務端,隱藏服務端的真實身份
如10086或者10010客服熱線,我們撥的同一個電話,但是對面的人大概率每次都不一樣,但他們都會說自己是客服,實際上我們也不關心是誰,我們只關心能不能解決自己的問題。
很顯然客服不止一個,我們撥過去後系統會選擇空閒的客服接線給我們,以保證不會出現長時間排隊的情況。
雖然代理了服務端,隱藏其身份,但這個不是目的。
目的是讓代理去選擇合適的服務端,只是沒打算告知客戶端罷了。對客戶端而言,互動物件是代理,並不知道代理的物件是誰
正向代理代理的物件是客戶端,反向代理代理的物件是服務端
主要是用在伺服器層面的,為了避免偏題,此處不再贅述,有興趣的可以去這兩個地方看看,講得不錯
https://blog.csdn.net/lixiangss1993/article/details/87934562
5.靜態代理與動態代理
上述步驟就是靜態代理,但存在以下缺陷
代理主題是為真實主題專門配備的,因此是一對一的關係,且使用同一個介面進行規範。
因此代理主題必須基於已有的真實主題,每新增一個真實主題需要新增一個代理主題。
動態代理的出現則是為了解決這種缺陷
動態代理利用反射機制在執行時建立代理類,客戶端呼叫代理來執行服務端方法,因此一個代理類適用於所有服務端。
package com.company.test.invocation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//抽象主題
interface Subject {
void Request(String s);
}
//真實主題
class RealSubject implements Subject {
@Override
public void Request(String s) {
System.out.println("訪問真實主題方法...");
}
}
//代理
class ProxyHandler implements InvocationHandler {
private Object object;
public ProxyHandler(Object object) {
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before invoke " + method.getName());
method.invoke(object, args);
System.out.println("After invoke " + method.getName());
return null;
}
}
public class InvocationTest {
public static void main(String[] args) {
Subject subject = new RealSubject();
Subject realSubject = (Subject) Proxy.newProxyInstance(subject.getClass().getClassLoader(), subject.getClass().getInterfaces(), new ProxyHandler(subject));
realSubject.Request("hello?");
}
}
通過這樣的處理方式,我們將subject
託管給ProxyHandler
代理。
我們可以在invoke
方法中進行前置處理,後置處理,甚至修改引數(args),或者直接丟擲異常
實際使用的過程中,我們通常使用AOP
(面向切面程式設計)以註解的方式進行代理,有興趣的可以看看之前我整理的JWT
的自定義切面攔截器
https://blog.csdn.net/qq_25978501/article/details/108409031
後記
代理模式真要學完可能得幾天時間,有空再整理細說吧,先做了解,實戰中會遇到不少的,特別是AOP
幾乎算是面試必問的了
作者:Echo_Ye
WX:Echo_YeZ
Email :[email protected]
個人站點:在搭了在搭了。。。(右鍵 - 新建資料夾)