23種設計模式:代理模式
阿新 • • 發佈:2021-08-05
代理模式 (Proxy Pattern)
1、介紹
在某些情況下,客戶不能或者不想直接訪問另一個物件,這時需要一個中介來幫忙完成任務。
- 客戶:訪問物件;
- 中介:代理;
- 被訪問物件:真實主題(目標物件);
例如:
-
買高鐵票不一定要去高鐵站,可以通過12306網站或者其他車票代售點買;
-
找工作、找房子等也可以通過找中介完成。
1.1、定義
訪問物件不適合或者不能直接引用目標物件,此時給目標物件提供一個代理以控制對該物件的訪問。代理物件作為訪問物件和目標物件之間的中介。
根據代理的建立時期,代理模式分為靜態代理和動態代理。
- 靜態:程式執行前。程式設計師在建立目標物件時建立代理類;
- 動態
1.2、應用場景
使用代理模式主要有兩個目的:保護和增強目標物件。
應用場景 | 說明 | 舉例 |
---|---|---|
遠端代理 | 隱藏目標物件存在於不同地址空間的事實,方便客戶端訪問 | 使用者申請網盤空間時,會在系統中建立一個虛擬硬碟,使用者通過虛擬硬碟實際訪問的是網盤空間。 |
虛擬代理 | 建立目標物件所需的開銷大 | 下載很大的檔案需要很長時間,此時可以先用小比例的虛擬代理替換真實的物件,消除使用者對伺服器慢的感覺。 |
安全代理 | 控制不同級別客戶對真實物件的訪問許可權 | |
智慧指引 | 呼叫目標物件時,附加一些額外的處理功能 | 增加計算真實物件的引用次數的功能,這樣當該物件沒有被引用時,就可以自動釋放它。 |
延遲載入 | 為了提高系統的效能,延遲對目標的載入 |
2、靜態代理
2.1、特點
優點:
- 在客戶端與目標物件之間,起到一箇中介作用和保護目標物件的作用;
- 可以擴充套件目標物件的功能;
- 將客戶端與目標物件分離,在一定程度上降低了系統的耦合度,增加了程式的可擴充套件性。
缺點:
- 會造成系統設計中類的數量增加:增加真實主題就要增加代理,程式碼量翻倍;
- 設計代理之前,必須先有真實主題(目標物件),不靈活;
- 在客戶端和目標物件之間增加一個代理物件,會造成請求處理速度變慢。
使用動態代理方式,即可解決以上的缺點。
2.2、結構及實現
- 抽象主題類(Subject):定義一個介面或抽象類,宣告要實現的業務方法;
- 真實主題類(Real Subject):實現了抽象主題中的具體業務,是代理物件引用的真實物件;
- 代理類(Proxy):實現了抽象主題類,提供一個介面來引用真實主題,可以訪問、控制和擴充套件真實主題的功能;
- 客戶端(Client):通過代理類訪問真實主題類的業務方法;
結構圖:
結構圖實現:
Subject
public interface Subject {
/**
* 業務方法
*/
void request();
}
RealSubject
public class RealSubject implements Subject{
@Override
public void request() {
System.out.println("真實物件:訪問真實主題的業務方法...");
}
}
Proxy
public class Proxy implements Subject {
/**
* 引用真實主題
*/
private Subject realSubject;
@Override
public void request() {
setRealSubject();
preRequest();
realSubject.request();
postRequest();
}
private void setRealSubject() {
if (realSubject == null) {
this.realSubject = new RealSubject();
}
}
private void preRequest() {
System.out.println("代理物件:訪問真實主題前的預處理...");
}
private void postRequest() {
System.out.println("代理物件:訪問真實主題後的後續處理...");
}
}
Client
public class Client {
public static void main(String[] args) {
Proxy proxy = new Proxy();
proxy.request();
}
}
執行結果
2.3、應用例項
通過房屋中介租房子。
Rent:租房業務
public interface Rent {
/**
* 租房業務
*/
void rent();
}
Landlord:房東
public class Landlord implements Rent {
@Override
public void rent() {
System.out.println("房東:出租房子");
}
}
Proxy:房屋中介
public class Proxy implements Rent {
private Landlord landlord;
@Override
public void rent() {
setLandlord();
visit();
landlord.rent();
pay();
}
private void pay() {
System.out.println("中介:收取成交後的中介費");
}
private void visit() {
System.out.println("中介:帶你看房子");
}
private void setLandlord() {
if (landlord == null) {
landlord = new Landlord();
}
}
}
Client
public class Client {
public static void main(String[] args) {
Proxy proxy = new Proxy();
proxy.rent();
}
}
執行結果
3、動態代理
動態代理也叫 JDK 代理、介面代理,相對靜態代理比較靈活。
3.1、特點
優點:
- 不需要實現介面;
- 通過反射機制,動態建立代理物件;
- 能在程式碼執行時動態地改變某個物件的代理,併為代理物件動態地增加方法和行為。
解決靜態代理的缺點:
- 會造成系統設計中類的數量增加:增加真實主題就要增加代理,程式碼量翻倍;
- 設計代理之前,必須先有真實主題;
- 在客戶端和目標物件之間增加一個代理物件,會造成請求處理速度變慢。
3.2、結構及實現
與靜態代理相同:
- 抽象主題類(Subject):定義一個介面或抽象類,宣告要實現的業務方法;
- 真實主題類(Real Subject):實現了抽象主題中的具體業務,是代理物件引用的真實物件;
- 代理類(Proxy):實現了抽象主題類,提供一個介面來引用真實主題,可以訪問、控制和擴充套件真實主題的功能;
- 客戶端(Client):通過代理類訪問真實主題類的業務方法;
結構圖:
結構圖實現:
Subject
public interface Subject {
/**
* 業務方法
*/
void request();
}
RealSubject
public class RealSubject1 implements Subject{
@Override
public void request() {
System.out.println("真實物件1:訪問真實主題的業務方法...");
}
}
public class RealSubject2 implements Subject{
@Override
public void request() {
System.out.println("真實物件2:訪問真實主題的業務方法...");
}
}
DynamicProxy
public class DynamicProxy implements InvocationHandler {
/**
* 真實主題
*/
private Object object;
/**
* 設定真實主題
*
* @param object 真實主題
*/
public void setObject(Object object) {
this.object = object;
}
/**
* 獲取代理物件
*
* @return 真實主題的代理物件
*/
public Object getProxy() {
return Proxy.newProxyInstance(object.getClass().getClassLoader(),
object.getClass().getInterfaces(),
this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
preRequest();
// 執行真實物件的方法
Object result = method.invoke(object, args);
postRequest();
return result;
}
private void preRequest() {
System.out.println("代理:預處理");
}
private void postRequest() {
System.out.println("代理:後續處理");
}
}
Client
public class Client {
public static void main(String[] args) {
// 獲取處理器
DynamicProxy handler = new DynamicProxy();
// 設定真實主題
handler.setObject(new RealSubject1());
// 獲取代理物件
Subject proxy = (Subject) handler.getProxy();
//
proxy.request();
}
}
執行結果
3.3、應用例項
在DAO層,增加日誌功能。
UserDao:相當於Subject
public interface UserDao {
/**
* 新增使用者
*/
void insertUser();
/**
* 刪除使用者
*/
void deleteUser();
/**
* 更新使用者
*/
void updateUser();
/**
* 查詢使用者
*/
void getUser();
}
UserDaoImpl:相當於RealSubject
public class UserDaoImpl implements UserDao {
@Override
public void insertUser() {
System.out.println("新增使用者...");
}
@Override
public void deleteUser() {
System.out.println("刪除使用者...");
}
@Override
public void updateUser() {
System.out.println("更新使用者...");
}
@Override
public void getUser() {
System.out.println("查詢使用者...");
}
}
DynamicProxy
public class DynamicProxy implements InvocationHandler {
/**
* 真實主題
*/
private Object object;
/**
* 設定真實主題
*
* @param object 真實主題
*/
public void setObject(Object object) {
this.object = object;
}
/**
* 獲取代理物件
*
* @return 真實主題的代理物件
*/
public Object getProxy() {
return Proxy.newProxyInstance(object.getClass().getClassLoader(),
object.getClass().getInterfaces(),
this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log(method.getName());
return method.invoke(object, args);
}
/**
* 日誌功能
*
* @param method 方法,通過反射獲取方法名
*/
private void log(String method) {
System.out.println("LOG:執行了" + method + "方法");
}
}
Client
public class Client {
public static void main(String[] args) {
DynamicProxy dynamicProxy = new DynamicProxy();
dynamicProxy.setObject(new UserDaoImpl());
UserDao proxy = (UserDao) dynamicProxy.getProxy();
proxy.insertUser();
proxy.deleteUser();
proxy.updateUser();
proxy.getUser();
}
}
執行結果
4、小結
- 代理物件作為訪問物件和目標物件之間的中介,控制對目標物件的訪問;
- 靜態代理在程式執行前手動建立,動態代理在程式執行時由反射機制動態建立;
- 代理模式可以起到中介和保護作用,擴充套件和增強目標物件的功能;
- 靜態代理不靈活,需要先有真實主題才能設計代理;代理與真實主題一一對應,即有一個主題就要有一個代理,增加程式碼量;
- 動態代理非常靈活,只需要一個動態代理類,不用隨著真實主題的增加而增加。