一.概述
代理模式也是平時比較常用的設計模式之一,代理模式其實就是提供了一個新的對象,實現了對真實對象的操作,或成爲真實對象的替身.在日常生活中也是很常見的.例如A要租房,爲了省麻煩A會去找中介,中介會替代A去篩選房子,A坐享中介篩選的結果,並且交房租也是交給中介,這就是一個典型的日常生活中代理模式的應用.平時打開網頁,最先開到的一般都是文字,而圖片等一些大的資源都會延遲加載,這裏也是使用了代理模式.
代理模式的組成:
Abstract Subject:抽象主題-聲明真實主題和代理主題共同的接口
Real Subject:真實主題-真實的對象,需要被代理主題引用
Proxy Subject:代理主題-因爲ProxySubject引用了RealSubject,並且實現了跟RealSubject一樣的接口,所以ProxySubject可以操作RealSubject,還可以提供一些附加操作,例如before & after
代理模式常用基於場景的分類:
1.Virtual Proxy:虛擬代理其實就是通過代理的模式對消耗資源比較大的對象做了一個延遲加載,就是什麼時候用到這個對象纔去創建它.
2.Remote Proxy:遠程代理是比較經典的應用了,類似於C/S模式(主要攔截並控制遠程方法的調用,做代理防火牆之類的).
3.Smart Reference Proxy:智能引用代理可以給引用的對象提供一些額外的操作,例如實現裏面中介Searching和Prepare contract的動作.
4.Access Proxy;保護代理可以控制一個對象的訪問,必要時候提供一系列的權限管理.
5.Copy-on-write Proxy:寫時拷貝(克隆)代理其實是Virtual Proxy的分支,提供了拷貝大對象的時候只有在對象真正變化後纔會進行拷貝(克隆)的操作(延遲拷貝).
代理模式的優缺點:
優點:
1.代理作爲調用着和真實對象的中間層,降低了模塊間和系統的耦合性
2.可以以一個小對象代理一個大對象,達到優化系統提高運行速度的目的
3.提供RealSubject的權限管理
4.容易擴展,RealSubject和ProxySubject都接口化了,RealSubject更改業務後只要接口不變,ProxySubject可以不做任何修改.
缺點:
1.同優點1,因爲調用者和真實對象多了一箇中間層,所以會增加調用響應的時間
二.實現
這裏就拿A找中介租房爲Demo來構建代理模式.
1.普通代理
根據場景先定義一個抽象主題,IHouse,提供三個方法,分別是獲取房屋信息,籤合同和付租金.
/**
* Created by jesse on 15-7-24.
*/
public interface IHouse {
void getHouseInfo();
void signContract();
void payFees();
}
接下來定義真實主題,並實現IHouse接口.增加房屋名稱和價格兩個屬性,填充藉口方法,在獲取房屋信息的時候就把房屋名稱和價格log出來;籤合同的時候log出籤合同的時間,付租金的時候log出價格.
public class House implements IHouse{
private final String TAG = House.class.getSimpleName();
private String name;
private double price;
public House(String name, double price){
this.name = name;
this.price = price;
}
@Override
public void getHouseInfo() {
Log.i(TAG, "House Info- name:" + name + " ¥:" + price);
}
@Override
public void signContract() {
Log.i(TAG, "Contract:" + name + " signed at" +
new SimpleDateFormat("HH:mm:ss").format(SystemClock.uptimeMillis()));
}
@Override
public void payFees() {
Log.i(TAG, "Bill: name-" + name + " $-" + price);
}
}
定義房屋代理,同樣需要實現IHouse接口,並持有House的引用.可以看到代理類其實就像有封裝House,提供了一些附加操作,例如客戶要看房子的時候代理會先檢索自己庫存的房屋信息,籤合同之前要準備合同之類的.
public class ProxyHouse implements IHouse{
private final String TAG = ProxyHouse.class.getSimpleName();
private IHouse house;
public ProxyHouse(IHouse house){
this.house = house;
}
@Override
public void getHouseInfo() {
Log.i(TAG, "searching");
house.getHouseInfo();
Log.i(TAG, "search finished");
}
@Override
public void signContract() {
Log.i(TAG, "prepare contract");
house.signContract();
}
@Override
public void payFees() {
house.payFees();
}
}
對於客戶來說,完全不用跟House進行直接交互,這裏先定義一個房子叫唐頓莊園,租金5k,建立一個房屋代理,把唐頓莊園委託給代理.客戶要找房子,籤合同,付租金直接找代理就行了.
IHouse house = new House("Downton Abbey", 5000);
IHouse proxyHouse = new ProxyHouse(house);
Log.i(TAG, "looking for a perfect house");
proxyHouse.getHouseInfo();
Log.i(TAG, "thinking");
proxyHouse.signContract();
proxyHouse.payFees();
Log.i(TAG, "so easy");
整個代理模式的流程可以從下面的時序圖展示出來.Client只跟代理進行交互.
2.虛擬代理
虛擬代理前面有介紹,就是基於代理模式又做了延遲加載來節省內存,但是如果某個對象要在多個沒有固定時序地方使用的時候就要進行判空,也會一定程度上犧牲性能(有點像代理模式+懶漢模式).這裏還是拿租房的例子來展示.
這裏就假設House是一個很龐大的對象,在創建的時候很耗費資源,那我們就更改成當Custom需要用它的時候纔去初始化.這裏就在ProxyHouse構造的時候先判House的引用是否爲空,然後纔會初始化House,當然如果這裏有多線程併發的話可以根據不同的場景進行加鎖或者雙檢鎖來保證線程安全.
public ProxyHouse(){
if (null == house)
house = new House("Downton Abbey", 5000);
}
IHouse proxyHouse = new ProxyHouse();
Log.i(TAG, "looking for a perfect house");
proxyHouse.getHouseInfo();
Log.i(TAG, "thinking");
proxyHouse.signContract();
proxyHouse.payFees();
Log.i(TAG, "so easy");
3.強制代理
強制代理是反其道而行之的代理模式,一般情況下代理模式都是通過代理來找到真實的對象,而強制代理則是通過真實對象才能找到代理也就是說由真實對象指定代理,當然最終訪問還是通過代理模式訪問的.從名字還能看出它跟其他代理的一個不同,就是強制用代理.拿上面普通代理的例子來說,Custom看不到實體的House的時候它只能通過代理來訪問,但是由於沒有限制,Custom也可以直接繞過ProxyHouse來訪問House,但是強制代理就多了一個限制,Custom必須通過ProxyHouse才能訪問House.就像一些房東嫌麻煩,有房客直接電話過來說要看房,房東給出一箇中介的電話說你跟中介聯繫吧.
首先需要在接口裏面添加一個獲取代理的接口
public interface IHouse {
void getHouseInfo();
void signContract();
void payFees();
IHouse getProxy();
}
真實對象實現接口,並在getProxy中實例化代理,同時在其他方法裏面做代理判斷,只有使用自身自定的代理纔會正常進行.
public class House implements IHouse{
private final String TAG = House.class.getSimpleName();
private String name;
private double price;
private IHouse proxy;
public House(String name, double price){
this.name = name;
this.price = price;
}
@Override
public void getHouseInfo() {
if (isProxy())
Log.i(TAG, "House Info- name:" + name + " ¥:" + price);
else
Log.i(TAG, "Please use correct proxy");
}
@Override
public void signContract() {
if (isProxy())
Log.i(TAG, "Contract:" + name + " signed at" +
new SimpleDateFormat("HH:mm:ss").format(SystemClock.uptimeMillis()));
else
Log.i(TAG, "Please use correct proxy");
}
@Override
public void payFees() {
if (isProxy())
Log.i(TAG, "Bill: name-" + name + " $-" + price);
else
Log.i(TAG, "Please use correct proxy");
}
@Override
public IHouse getProxy() {
if (null == proxy)
proxy = new ProxyHouse(this);
return proxy;
}
private boolean isProxy(){
if (null == proxy)
return false;
else
return true;
}
}
如果這個時候直接操作House對象,或者通過Custom構建的代理來訪問都會返回以下結果
所以我們必須使用由真實對象指定的代理纔可以正常得訪問.
IHouse house = new House("Downton Abbey", 5000);
house = house.getProxy();
Log.i(TAG, "looking for a perfect house");
house.getHouseInfo();
Log.i(TAG, "thinking");
house.signContract();
house.payFees();
但是這裏的強制代理有個Bug,強制代理其實並沒有生效,Custom還是可以直接訪問House,例如我通過下面的方式來進行訪問,只是通過getProxy創建並獲取代理,但是我不用代理還是直接用House的實例進行訪問,這個時候還是可以正常訪問的.後續會想辦法解了這個Bug並且更新上來的.
IHouse house = new House("Downton Abbey", 5000);
house.getProxy();//這裏只是通過getProxy創建出代理
Log.i(TAG, "looking for a perfect house");
house.getHouseInfo();
Log.i(TAG, "thinking");
house.signContract();
house.payFees();
4.動態代理
上面介紹的都是自己先寫好的代理類,這樣代理關係都是固定的,當代理多個真實對象的時候就要寫多個代理類,並且會產生冗餘的代碼,擴展性和可維護性都不高,而動態代理是基於反射實現了在程序運行的過程中才決定代理什麼對象.像AOP的核心思想就是動態代理.(這裏使用的是Java的動態代理)既然是動態代理就不需要ProxyHouse也不需要實現IHouse接口了,這裏寫一個ProxyHandler實現InvocationHandler的invoke接口,並且提供一個根據Proxy構建出來的代理實例給Custom.在通過反射調用真實對象具體的方法之前打印出該方法的名字.
public class ProxyHandler implements InvocationHandler{
private final String TAG = ProxyHandler.class.getSimpleName();
Object targetObj;
public Object newProxyInstance(Object targetObj){
this.targetObj = targetObj;
return Proxy.newProxyInstance(targetObj.getClass().getClassLoader(),
targetObj.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object ret;
Log.i(TAG, "method name:" + method.getName());
ret = method.invoke(targetObj, args);
return ret;
}
}
ProxyHandler proxy = new ProxyHandler();
IHouse house = (IHouse) proxy.newProxyInstance(new House("Downton Abbey", 5000));
Log.i(TAG, "looking for a perfect house");
house.getHouseInfo();
Log.i(TAG, "thinking");
house.signContract();
house.payFees();
Log.i(TAG, "so easy");
從結果可以看出在真正invoke真實對象的方法之前都會打印出方法名,也可以在這裏做一些其他的對象控制.
這個時候整個過程的時序圖就變成下面的樣子了,通過JDK的Proxy對象和反射的機制來支撐起來動態代理的核心功能.