引言
在古代《三國志·諸葛亮傳》中有這么一句話——“政事無巨細咸于亮。”;在唐代張九齡的《謝賜大麥面狀》中還有這么一句——“勤儉于生人,事必躬親,動合天德兩個典故連起來就是“事無巨細,事必躬親”,意思是無論大事小事都要親自過問,親力親為,雖然這樣的精神及其可貴。但在我們當代的生活中如果我們皆事必躬親的話,那么我們的生活將會是十分疲倦的,畢竟任何人的精力都是有限的,每一個人或事都有自己擅長的事或工作,同樣地我們的程序也是如此,每一個業務類都應該只專注于核心功能,而把其他繁雜的功能外包出去給代理類,比如說我們找房子,我們核心主題就是找到房子租下,如果我們自己找的話,得一家家聯系,商討最后決定價格、簽約付款,而采用代理模式的話就讓代理者去完成這些繁瑣的細節,作為客戶端我們甚至不需要知道中間的細節。
一、代理模式概述
代理模式(Proxy Pattern)又叫委托模式屬于是一個使用率非常高結構型設計模式。其定義如下:為其他對象提供一種代理以控制對這個對象的訪問。(Provide a surrogate or placeholder for another object to control access to it.)通俗可以理解成代理代理,就是幫你打理。你只跟代理一個人打交到。而不關心實際操作的人的具體如何做。代理模式目前框架里用的最多,主要作用是程序本身不關心被代理的身份細節。而只關心它暴露出來的共有行為接口,這個代理之所以理解困難,是有時候它像個工廠模式,有時候它像適配模式。也可從字面理解就是代理你去執行調用別的類的方法面向被調用的類。所以代理模式一般都會涉及到四個角色:抽象主題、繼承或實現抽象主題的真實主題、代理類、客戶類。
抽象主題——負責聲明真實主題和代理的共同接口方法,同樣的可以定義為接口或抽象類
繼承或實現抽象主題的真實主題——定義了代理所表示的真實對象,由其執行具體的業務邏輯方法,也就是說當客戶類通過代理類去執行操作時,本質就是調用真實主題的方法,所以這個類又被稱為被委托類或被代理類。
代理類——該類會持有真實主題類的引用,并在其所實現的接口方法中完成對真實主題類的相關方法的調用(即為其他對象提供一種代理以控制對這個對象的訪問),所以這個類又被稱為委托類或代理類。
- 客戶類——你前面這個類構建出了代理模式,總得有人使用吧,這個所謂的客戶類,簡單來說就是你使用代理模式的那個類。
二、代理模式的優點和缺點及可用場景
1、代理模式的共同優點
能夠協調調用者和被調用者,在一定程度上降低了系統的耦合度。
客戶端可以針對抽象主題角色進行編程,增加和更換代理類無須修改源代碼,符合開閉原則,系統具有較好的靈活性和可擴展性。
遠程代理為位于兩個不同地址空間對象的訪問提供了一種實現機制,可以將一些消耗資源較多的對象和操作移至性能更好的計算機上,提高系統的整體運行效率。
虛擬代理通過一個消耗資源較少的對象來代表一個消耗資源較多的對象,可以在一定程度上節省系統的運行開銷。
緩沖代理為某一個操作的結果提供臨時的緩存存儲空間,以便在后續使用中能夠共享這些結果,優化系統性能,縮短執行時間。
保護代理可以控制對一個對象的訪問權限,為不同用戶提供不同級別的使用權限。
2、代理模式的缺點
- 由于在客戶端和真實主題之間增加了代理對象,因此有些類型的代理模式可能會造成請求的處理速度變慢,例如保護代理。
- 實現代理模式需要額外的工作,而且有些代理模式的實現過程較為復雜,例如遠程代理。
3、適用場景
當無法或者不想直接訪問某個對象或訪問直接某個對象消耗巨大時,可以采取通過一個代理對象來間接訪問,為了保持對客戶端透明,代理對象和被代理對象需要實現相同的接口。
當客戶端對象需要訪問遠程主機中的對象時可以使用遠程代理。
當需要用一個消耗資源較少的對象來代表一個消耗資源較多的對象,從而降低系統開銷、縮短運行時間時可以使用虛擬代理,例如一個對象需要很長時間才能完成加載時。
當需要為某一個被頻繁訪問的操作結果提供一個臨時存儲空間,以供多個客戶端共享訪問這些結果時可以使用緩沖代理。通過使用緩沖代理,系統無須在客戶端每一次訪問時都重新執行操作,只需直接從臨時緩沖區獲取操作結果即可。
當需要控制對一個對象的訪問,為不同用戶提供不同級別的訪問權限時可以使用保護代理。
當需要為一個對象的訪問(引用)提供一些額外的操作時可以使用智能引用代理。
三、代理模式的實現形式
通常按照代碼機制可以分為靜態代理和動態代理兩大類,而按照使用范圍可以分為以下常用的四種:遠程代理(Remote Proxy)、虛擬代理(Virtual Proxy)、保護代理(Protection Proxy)和智能引用(Smart Reference,又叫計數代理),值得注意的是兩大分類機制并不獨立,就是說無論是靜態代理還是動態代理都可以采用以上四種形式,實現起來也很簡單,無論是什么形式步驟和基本思想都大同小異,以法律代理簡化流程為例。
1、靜態代理
1.1、根據具體業務定義自己的抽象主題
可以定義成抽象類也可以定義成接口。
package proxy;
public interface ILegalSuit {
void submit();//提交訴訟申請
void proof();//進行舉證
void debate();//法庭辯護
}
1.2、繼承或實現自抽象主題的真實主題
真實主題其實就是被代理對象。
/**
*
* @author cmo
*訴訟人,本質上這些訴訟流程的具體事宜都是由訴訟人去定義實現的
*/
public class LegalSuiter implements ILegalSuit {
public void submit() {
System.out.println("起草訴訟書并提交至法院");
}
public void proof() {
System.out.println("采集證據并向法院舉證");
}
public void debate() {
System.out.println("庭內辯護");
}
}
1.3、繼承或實現自抽象主題的代理對象
實現代理對象主要步驟可以分為四步,第一,繼承或實現自抽象主題,第二,持有被代理對象的引用并通過構造方法傳入,第三,通過引用完成對被代理對象方法的調用,四,根據業務新增自己的邏輯。
package proxy;
/**
*
* @author cmo
*訴訟代理類,一般代理類的方法實現很簡單就是持有被代理對象,
*并且實現抽象主題的方法里使用被代理對象的引用調用被代理類的方法,
*當然代理類中還可以定義其他的方法邏輯,也可以在調用被代理對像的方法前后增刪邏輯
*/
public class LegalSuiterProxyer implements ILegalSuit {
private ILegalSuit suiter;
//通過構造方法把被代理對象傳遞過來
public LegalSuiterProxyer(ILegalSuit suiter){
this.suiter=suiter;
}
@Override
public void submit() {
suiter.submit();
}
@Override
public void proof() {
suiter.proof();
}
@Override
public void debate() {
suiter.debate();
}
}
測試
public class Client {
/**
* @param args
*/
public static void main(String[] args) {
ILegalSuit suiter=new LegalSuiter();//首先構造一個被代理對象
LegalSuiterProxyer proxyer=new LegalSuiterProxyer(suiter);//創建一個與被代理對象綁定的代理對象
proxyer.submit();
proxyer.proof();
proxyer.debate();
}
}
前面說過代理模式又叫做委托模式,因為代理模式本質上就是一個委托機制,被代理對象將方法的執行委托給代理對象,客戶端想要去完成相關操作的時候不需要直接去調用被代理對象,只需要簡單地去找代理對象即可,而且擴展性也很高,比如說某天前代理人的訴訟已經完成了,那么代理人不可能也不工作了吧,他還想幫別人代理訴訟的話,采用代理模式就顯示出很大的優勢,要做的只是再新增一個被代理對象并傳入到代理對象中即可,完全不會影響到前代理人的邏輯,高度解耦。那么何謂靜態代理呢,從上例可以得出,代理對象和被代理對象并不是1:1的關系,代理對象可以代理多個相似的被代理對象,他們之中有一個明顯特征:在我們客戶端代碼工作前,被代理對象的代碼已知(是由程序員自己開發或者第三方自動生成),通俗來說就是在編碼階段我們就已經知道了這個代理類將代理誰。,這就是靜態代理。
2、動態代理
而動態代理則與靜態代理相反,在編碼階段我們不知道被代理對象是誰,被代理對象是在運行階段動態生成的(一般是通過反射機制),動態代理的實現十分簡單,因為JDK 自身已經為我提供了一個便捷的動態代理接口InvocationHandler,我們只需要實現該接口并重寫invoke方法即可,前面步驟和靜態代理差不多(略),區別在代理類的實現。
- 實現動態代理接口InvocationHandler
- 持有被代理對象的引用,由于被代理對象未知,所以使用Object類型
- 通過構造方法傳遞被代理對象的引用
- 重寫invoke方法,并在方法中通過反射調用被代理類的方法
package proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class DynamicProxyer implements InvocationHandler {
private Object obj;//被代理對象的引用
public DynamicProxyer(Object obj) {
this.obj = obj;
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object result=method.invoke(obj, args);//調用被代理對象的方法
return null;
}
}
測試
import java.lang.reflect.Proxy;
public class DynamicClient {
/**
* @param args
*/
public static void main(String[] args) {
ILegalSuit suiter=new LegalSuiter();//構造一個被代理對象
DynamicProxyer dynaProxyer=new DynamicProxyer(suiter);//構造一個動態代理
ClassLoader loader=suiter.getClass().getClassLoader();
//動態構造一個具體的代理對象,在這綁定上了被代理對象
ILegalSuit dynaSuiter=(ILegalSuit) Proxy.newProxyInstance(loader, new Class[]{ILegalSuit.class}, dynaProxyer);
dynaSuiter.submit();
dynaSuiter.proof();
dynaSuiter.debate();
}
}
由以上例子不難得出,動態代理其實是同一個動態代理類來來代理N多個不同的代理類,為的就是對被代理者和代理者解耦,而靜態代理這是只能在開發階段就決定了被代理者。
1、遠程代理
為位于兩個不同地址空間對象的訪問提供了一種實現機制,可以將一些消耗資源較多的對象和操作移至性能更好的計算機上,提高系統的整體運行效率。
2、虛擬代理
虛擬代理的基本思想其實和延遲初始化相似,如果需要創建一個資源消耗較大的復雜對象,先創建一個小的對象來表示,等到真正需要用的時候才會創建真實的對象。當用戶請求一個大的對象時候,虛擬代理暫時充當真實對象的角色,待該真實對象真正被創建出來之后,虛擬代理就會將用戶請求委托給真是對象。簡而言之,使用一個代理對象暫時表示一個消耗巨大資源的真實對象,而真是對象僅僅在真正使用的時候才被創建。
虛擬代理模式(Virtual Proxy)是一種節省內存的技術,它建議創建那些占用大量內存或處理復雜的對象時,把創建這類對象推遲到使用它的時候。在特定的應用中,不同部分的功能由不同的對象組成,應用啟動的時候,不會立即使用所有的對象。在這種情況下,虛擬代理模式建議推遲對象的創建直到應用程序需要它為止。對象被應用第一次引用時創建并且同一個實例可以被重用。這種方法優缺點并存。
2.1、虛擬代理的優點
這種方法的優點是,在應用程序啟動時,由于不需要創建和裝載所有的對象,因此加速了應用程序的啟動。
2.1、虛擬代理的缺點
因為不能保證特定的應用程序對象被創建,在訪問這個對象的任何地方,都需要檢測確認它不是空(null)。也就是,這種檢測的時間消耗是最大的缺點。
2.3、虛擬代理的使用逐一實現
應用虛擬代理模式,需要設計一個與真實對象具有相同接口的單獨對象(指虛擬代理)。不同的客戶對象可以在創建和使用真實對象地方用相應的虛擬對象來代替。虛擬對象把真實對象的引用作為它的實例變量維護。代理對象不要自動創建真實對象,當客戶需要真實對象的服務時,調用虛擬代理對象上的方法,并且檢測真實對象是否被創建。如果真實對象已經創建,代理把調用轉發給真實對象,如果真實對象沒有被創建:
代理對象創建真實對象
代理對象把這個對象分配給引用變量。
代理把調用轉發給真實對象
按照這種安排,驗證對象存在和轉發方法調用這些細節對于客戶是不可見的。客戶對象就像和真實對象一樣與代理對象進行交互。因此客戶從檢測真實對象是否為null中解脫出來,另外,由于創建代理對象在時間和處理復雜度上要少于創建真實對象。因此,在應用程序啟動的時候,用代理對象代替真實對象初始化。
3、保護代理
當原始對象有不同的訪問權限時,使用代理控制對原始對象的訪問。可以控制對一個對象的訪問權限,為不同用戶提供不同級別的使用權限。
4、智能引用
在訪問原始對象時執行一些代理自己附加的操作并對原始對象的引用計數。
未完待續...