Java設(shè)計(jì)模式-代理模式

簡(jiǎn)介
代理模式是一種結(jié)構(gòu)型設(shè)計(jì)模式,它可以讓我們通過一個(gè)代理對(duì)象來訪問一個(gè)真實(shí)的目標(biāo)對(duì)象,從而實(shí)現(xiàn)對(duì)目標(biāo)對(duì)象的功能擴(kuò)展或保護(hù)。代理模式的主要角色有三個(gè):
抽象主題(Subject):定義了真實(shí)主題和代理主題的公共接口,使得在任何使用真實(shí)主題的地方都可以使用代理主題。
真實(shí)主題(RealSubject):實(shí)現(xiàn)了抽象主題的接口,定義了真實(shí)的業(yè)務(wù)邏輯,是代理主題所代表的真實(shí)對(duì)象。
代理主題(Proxy):也實(shí)現(xiàn)了抽象主題的接口,但是在調(diào)用真實(shí)主題的方法之前或之后,可以執(zhí)行一些額外的操作,從而對(duì)真實(shí)主題進(jìn)行控制或增強(qiáng)。
代理模式可以幫助我們解決以下幾種問題:
當(dāng)我們無法或不想直接訪問一個(gè)對(duì)象時(shí),可以通過一個(gè)代理對(duì)象來間接訪問,例如遠(yuǎn)程代理、虛擬代理等。
當(dāng)我們想要給一個(gè)對(duì)象提供額外的功能時(shí),可以通過一個(gè)代理對(duì)象來實(shí)現(xiàn),而不需要修改原有的對(duì)象,例如緩存代理、日志代理等。
當(dāng)我們想要給一個(gè)對(duì)象增加一些訪問控制或安全保護(hù)時(shí),可以通過一個(gè)代理對(duì)象來實(shí)現(xiàn),例如防火墻代理、權(quán)限代理等。
實(shí)現(xiàn)
根據(jù)代理模式的定義,我們可以用以下的類圖來表示它的結(jié)構(gòu):

其中,Client是客戶端類,它需要使用Subject接口提供的方法。Proxy是代理類,它持有一個(gè)RealSubject的引用,并且實(shí)現(xiàn)了Subject接口。RealSubject是真實(shí)類,它也實(shí)現(xiàn)了Subject接口,并且定義了具體的業(yè)務(wù)邏輯。
代理模式有多種類型,例如靜態(tài)代理、動(dòng)態(tài)代理等,代理模式也有自己的優(yōu)缺點(diǎn),使用時(shí)需要根據(jù)具體的場(chǎng)景和需求來選擇合適的類型和方式。
靜態(tài)代理實(shí)現(xiàn)
下面我們用Java代碼來實(shí)現(xiàn)一個(gè)靜態(tài)代理的例子:
// 抽象主題接口
public interface Subject {
? ?// 定義一個(gè)抽象方法
? ?void request();
}
// 真實(shí)主題類
public class RealSubject implements Subject {
? ?// 實(shí)現(xiàn)抽象方法
? ?@Override
? ?public void request() {
? ? ? ?// 真實(shí)的業(yè)務(wù)邏輯
? ? ? ?System.out.println("RealSubject is doing something...");
? ?}
}
// 代理主題類
public class Proxy implements Subject {
? ?// 持有一個(gè)真實(shí)主題的引用
? ?private RealSubject realSubject;
? ?// 構(gòu)造方法,傳入一個(gè)真實(shí)主題對(duì)象
? ?public Proxy(RealSubject realSubject) {
? ? ? ?this.realSubject = realSubject;
? ?}
? ?// 實(shí)現(xiàn)抽象方法
? ?@Override
? ?public void request() {
? ? ? ?// 在調(diào)用真實(shí)主題之前,可以執(zhí)行一些額外操作
? ? ? ?System.out.println("Proxy is doing something before...");
? ? ? ?// 調(diào)用真實(shí)主題的方法
? ? ? ?realSubject.request();
? ? ? ?// 在調(diào)用真實(shí)主題之后,可以執(zhí)行一些額外操作
? ? ? ?System.out.println("Proxy is doing something after...");
? ?}
}
// 客戶端類
public class Client {
? ?public static void main(String[] args) {
? ? ? ?// 創(chuàng)建一個(gè)真實(shí)主題對(duì)象
? ? ? ?RealSubject realSubject = new RealSubject();
? ? ? ?// 創(chuàng)建一個(gè)代理對(duì)象,并傳入真實(shí)主題對(duì)象
? ? ? ?Proxy proxy = new Proxy(realSubject);
? ? ? ?// 使用代理對(duì)象來調(diào)用抽象方法
?? ?proxy.request();
}
}運(yùn)行結(jié)果如下:
Proxy is doing something before...
RealSubject is doing something...
Proxy is doing something after...
從運(yùn)行結(jié)果可以看出,代理對(duì)象在調(diào)用真實(shí)對(duì)象的方法之前和之后,都執(zhí)行了一些額外的操作,從而對(duì)真實(shí)對(duì)象進(jìn)行了增強(qiáng)或控制。
動(dòng)態(tài)代理實(shí)現(xiàn)
動(dòng)態(tài)代理是一種特殊的代理模式,它可以在運(yùn)行時(shí)動(dòng)態(tài)地創(chuàng)建代理對(duì)象,而不需要事先定義代理類。動(dòng)態(tài)代理可以更靈活地適應(yīng)不同的場(chǎng)景和需求,但是也更復(fù)雜和難以理解。
這個(gè)例子是使用JDK動(dòng)態(tài)代理來實(shí)現(xiàn)一個(gè)日志代理,它可以在調(diào)用目標(biāo)對(duì)象的方法之前和之后,記錄相關(guān)的日志信息。代碼如下:
// 抽象主題接口
public interface Subject {
? ?// 定義一個(gè)抽象方法
? ?void request();
}
// 真實(shí)主題類
public class RealSubject implements Subject {
? ?// 實(shí)現(xiàn)抽象方法
? ?@Override
? ?public void request() {
? ? ? ?// 真實(shí)的業(yè)務(wù)邏輯
? ? ? ?System.out.println("RealSubject is doing something...");
? ?}
}
// 日志處理器類,實(shí)現(xiàn)了InvocationHandler接口,用于定義代理邏輯
public class LogHandler implements InvocationHandler {
? ?// 持有一個(gè)目標(biāo)對(duì)象的引用
? ?private Object target;
? ?// 構(gòu)造方法,傳入一個(gè)目標(biāo)對(duì)象
? ?public LogHandler(Object target) {
? ? ? ?this.target = target;
? ?}
? ?// 實(shí)現(xiàn)invoke方法,用于調(diào)用目標(biāo)對(duì)象的方法,并在之前和之后執(zhí)行日志操作
? ?@Override
? ?public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
? ? ? ?// 在調(diào)用目標(biāo)對(duì)象之前,記錄開始時(shí)間
? ? ? ?long startTime = System.currentTimeMillis();
? ? ? ?System.out.println("開始執(zhí)行" + method.getName() + "方法...");
? ? ? ?// 調(diào)用目標(biāo)對(duì)象的方法,并獲取返回值
? ? ? ?Object result = method.invoke(target, args);
? ? ? ?// 在調(diào)用目標(biāo)對(duì)象之后,記錄結(jié)束時(shí)間和耗時(shí)
? ? ? ?long endTime = System.currentTimeMillis();
? ? ? ?long duration = endTime - startTime;
? ? ? ?System.out.println("結(jié)束執(zhí)行" + method.getName() + "方法,耗時(shí)" + duration + "毫秒");
? ? ? ?// 返回結(jié)果
? ? ? ?return result;
? ?}
}
// 客戶端類
public class Client {
? ?public static void main(String[] args) {
? ? ? ?// 創(chuàng)建一個(gè)真實(shí)主題對(duì)象
? ? ? ?RealSubject realSubject = new RealSubject();
? ? ? ?// 創(chuàng)建一個(gè)日志處理器對(duì)象,并傳入真實(shí)主題對(duì)象
? ? ? ?LogHandler logHandler = new LogHandler(realSubject);
? ? ? ?// 使用Proxy類的靜態(tài)方法newProxyInstance來動(dòng)態(tài)地創(chuàng)建一個(gè)代理對(duì)象,傳入真實(shí)主題對(duì)象的類加載器、接口和處理器
? ? ? ?Subject proxy = (Subject) Proxy.newProxyInstance(realSubject.getClass().getClassLoader(), realSubject.getClass().getInterfaces(), logHandler);
? ? ? ?// 使用代理對(duì)象來調(diào)用抽象方法
? ? ? ?proxy.request();
? ?}
}
運(yùn)行結(jié)果如下:
開始執(zhí)行request方法...
RealSubject is doing something...
結(jié)束執(zhí)行request方法,耗時(shí)1毫秒
從運(yùn)行結(jié)果可以看出,代理對(duì)象在調(diào)用真實(shí)對(duì)象的方法之前和之后,都執(zhí)行了一些日志操作,從而對(duì)真實(shí)對(duì)象進(jìn)行了增強(qiáng)。
優(yōu)缺點(diǎn)
靜態(tài)代理模式
優(yōu)點(diǎn):
代理模式可以實(shí)現(xiàn)對(duì)真實(shí)對(duì)象的功能擴(kuò)展或保護(hù),而不需要修改原有的對(duì)象,符合開閉原則。
代理模式可以實(shí)現(xiàn)對(duì)真實(shí)對(duì)象的訪問控制或延遲加載,提高系統(tǒng)的性能和安全性。
代理模式可以實(shí)現(xiàn)對(duì)真實(shí)對(duì)象的透明訪問,客戶端只需要使用抽象主題的接口,而不需要關(guān)心真實(shí)對(duì)象和代理對(duì)象的細(xì)節(jié)。
缺點(diǎn):
代理模式會(huì)增加系統(tǒng)的復(fù)雜度和開銷,因?yàn)樾枰獎(jiǎng)?chuàng)建和維護(hù)代理對(duì)象。
代理模式可能會(huì)降低系統(tǒng)的響應(yīng)速度,因?yàn)槊看握{(diào)用真實(shí)對(duì)象的方法都需要經(jīng)過代理對(duì)象。
動(dòng)態(tài)代理模式
優(yōu)點(diǎn):
動(dòng)態(tài)代理可以在運(yùn)行時(shí)動(dòng)態(tài)地創(chuàng)建代理對(duì)象,而不需要事先定義代理類,這樣可以減少代碼量和提高開發(fā)效率。
動(dòng)態(tài)代理可以根據(jù)不同的目標(biāo)對(duì)象和需求,靈活地生成不同的代理對(duì)象,這樣可以增加系統(tǒng)的可擴(kuò)展性和可維護(hù)性。
動(dòng)態(tài)代理可以實(shí)現(xiàn)對(duì)目標(biāo)對(duì)象的透明訪問,客戶端只需要使用抽象主題的接口,而不需要關(guān)心真實(shí)對(duì)象和代理對(duì)象的細(xì)節(jié)。
缺點(diǎn):
動(dòng)態(tài)代理需要使用反射和字節(jié)碼技術(shù)來生成代理對(duì)象,這樣會(huì)增加系統(tǒng)的復(fù)雜度和開銷,也可能會(huì)影響系統(tǒng)的性能和穩(wěn)定性。
動(dòng)態(tài)代理需要遵循一些約束和限制,例如JDK動(dòng)態(tài)代理只能代理實(shí)現(xiàn)了接口的類,CGLIB動(dòng)態(tài)代理不能代理final類或方法等,這樣會(huì)降低系統(tǒng)的靈活性和通用性。
動(dòng)態(tài)代理比靜態(tài)代理更難以理解和掌握,需要有一定的基礎(chǔ)知識(shí)和經(jīng)驗(yàn)才能使用好動(dòng)態(tài)代理。
運(yùn)用場(chǎng)景
當(dāng)我們需要訪問一個(gè)遠(yuǎn)程對(duì)象時(shí),可以使用遠(yuǎn)程代理,它可以隱藏遠(yuǎn)程對(duì)象的位置和通信細(xì)節(jié),讓客戶端像訪問本地對(duì)象一樣訪問遠(yuǎn)程對(duì)象。
當(dāng)我們需要?jiǎng)?chuàng)建一個(gè)開銷很大的對(duì)象時(shí),可以使用虛擬代理,它可以在真正需要的時(shí)候才創(chuàng)建真實(shí)對(duì)象,從而實(shí)現(xiàn)延遲加載和節(jié)省資源。
當(dāng)我們需要給一個(gè)對(duì)象增加一些額外的功能時(shí),可以使用裝飾代理,它可以在不修改原有對(duì)象的情況下,給對(duì)象添加一些新的行為或?qū)傩浴?/p>
當(dāng)我們需要給一個(gè)對(duì)象增加一些訪問控制或安全保護(hù)時(shí),可以使用保護(hù)代理,它可以根據(jù)不同的用戶或角色,對(duì)對(duì)象的訪問進(jìn)行限制或檢查。
當(dāng)我們需要給一個(gè)對(duì)象增加一些日志記錄或性能監(jiān)控時(shí),可以使用日志代理或性能代理,它可以在調(diào)用對(duì)象的方法之前或之后,記錄相關(guān)的信息或數(shù)據(jù)。