Android設(shè)計(jì)模式之代理模式Proxy淺顯易懂的詳細(xì)說明
一.概述
代理模式也是平時(shí)比較常用的設(shè)計(jì)模式之一,代理模式其實(shí)就是提供了一個(gè)新的對(duì)象,實(shí)現(xiàn)了對(duì)真實(shí)對(duì)象的操作,或成為真實(shí)對(duì)象的替身.在日常生活中也是很常見的.例如A要租房,為了省麻煩A會(huì)去找中介,中介會(huì)替代A去篩選房子,A坐享中介篩選的結(jié)果,并且交房租也是交給中介,這就是一個(gè)典型的日常生活中代理模式的應(yīng)用.平時(shí)打開網(wǎng)頁,最先開到的一般都是文字,而圖片等一些大的資源都會(huì)延遲加載,這里也是使用了代理模式.
代理模式的組成
Abstract Subject:抽象主題-聲明真實(shí)主題和代理主題共同的接口
Real Subject:真實(shí)主題-真實(shí)的對(duì)象,需要被代理主題引用
Proxy Subject:代理主題-因?yàn)镻roxySubject引用了RealSubject,并且實(shí)現(xiàn)了跟RealSubject一樣的接口,所以ProxySubject可以操作RealSubject,還可以提供一些附加操作,例如before & after

代理模式常用基于場(chǎng)景的分類
1.Virtual Proxy:
虛擬代理其實(shí)就是通過代理的模式對(duì)消耗資源比較大的對(duì)象做了一個(gè)延遲加載,就是什么時(shí)候用到這個(gè)對(duì)象才去創(chuàng)建它.
2.Remote Proxy:遠(yuǎn)程代理是比較經(jīng)典的應(yīng)用了,類似于C/S模式(主要攔截并控制遠(yuǎn)程方法的調(diào)用,做代理防火墻之類的).
3.Smart Reference Proxy:智能引用代理可以給引用的對(duì)象提供一些額外的操作,例如實(shí)現(xiàn)里面中介Searching和Prepare contract的動(dòng)作.
4.Access Proxy;保護(hù)代理可以控制一個(gè)對(duì)象的訪問,必要時(shí)候提供一系列的權(quán)限管理.
5.Copy-on-write Proxy:寫時(shí)拷貝(克隆)代理其實(shí)是Virtual Proxy的分支,提供了拷貝大對(duì)象的時(shí)候只有在對(duì)象真正變化后才會(huì)進(jìn)行拷貝(克隆)的操作(延遲拷貝).
代理模式的優(yōu)缺點(diǎn):
優(yōu)點(diǎn):
1.代理作為調(diào)用著和真實(shí)對(duì)象的中間層,降低了模塊間和系統(tǒng)的耦合性
2.可以以一個(gè)小對(duì)象代理一個(gè)大對(duì)象,達(dá)到優(yōu)化系統(tǒng)提高運(yùn)行速度的目的
3.提供RealSubject的權(quán)限管理
4.容易擴(kuò)展,RealSubject和ProxySubject都接口化了,RealSubject更改業(yè)務(wù)后只要接口不變,ProxySubject可以不做任何修改.
缺點(diǎn):
1.同優(yōu)點(diǎn)1,因?yàn)檎{(diào)用者和真實(shí)對(duì)象多了一個(gè)中間層,所以會(huì)增加調(diào)用響應(yīng)的時(shí)間
二.實(shí)現(xiàn)
這里就拿A找中介租房為Demo來構(gòu)建代理模式.
1.普通代理
根據(jù)場(chǎng)景先定義一個(gè)抽象主題,IHouse,提供三個(gè)方法,分別是獲取房屋信息,簽合同和付租金.
/**
* Created by jesse on 15-7-24.
*/
public interface IHouse {
void getHouseInfo();
void signContract();
void payFees();
}
接下來定義真實(shí)主題,并實(shí)現(xiàn)IHouse接口.增加房屋名稱和價(jià)格兩個(gè)屬性,填充借口方法,在獲取房屋信息的時(shí)候就把房屋名稱和價(jià)格log出來;簽合同的時(shí)候log出簽合同的時(shí)間,付租金的時(shí)候log出價(jià)格.
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);
}
}
定義房屋代理,同樣需要實(shí)現(xiàn)IHouse接口,并持有House的引用.可以看到代理類其實(shí)就像有封裝House,提供了一些附加操作,例如客戶要看房子的時(shí)候代理會(huì)先檢索自己庫存的房屋信息,簽合同之前要準(zhǔn)備合同之類的.
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();
}
}
對(duì)于客戶來說,完全不用跟House進(jìn)行直接交互,這里先定義一個(gè)房子叫唐頓莊園,租金5k,建立一個(gè)房屋代理,把唐頓莊園委托給代理.客戶要找房子,簽合同,付租金直接找代理就行了.
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");

整個(gè)代理模式的流程可以從下面的時(shí)序圖展示出來.Client只跟代理進(jìn)行交互.

2.虛擬代理
虛擬代理前面有介紹,就是基于代理模式又做了延遲加載來節(jié)省內(nèi)存,但是如果某個(gè)對(duì)象要在多個(gè)沒有固定時(shí)序地方使用的時(shí)候就要進(jìn)行判空,也會(huì)一定程度上犧牲性能(有點(diǎn)像代理模式+懶漢模式).這里還是拿租房的例子來展示.
這里就假設(shè)House是一個(gè)很龐大的對(duì)象,在創(chuàng)建的時(shí)候很耗費(fèi)資源,那我們就更改成當(dāng)Custom需要用它的時(shí)候才去初始化.這里就在ProxyHouse構(gòu)造的時(shí)候先判House的引用是否為空,然后才會(huì)初始化House,當(dāng)然如果這里有多線程并發(fā)的話可以根據(jù)不同的場(chǎng)景進(jìn)行加鎖或者雙檢鎖來保證線程安全.
public ProxyHouse(){
if (null == house)
house = new House("Downton Abbey", 5000);
}
[java] view plain copy
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.強(qiáng)制代理
強(qiáng)制代理是反其道而行之的代理模式,一般情況下代理模式都是通過代理來找到真實(shí)的對(duì)象,而強(qiáng)制代理則是通過真實(shí)對(duì)象才能找到代理也就是說由真實(shí)對(duì)象指定代理,當(dāng)然最終訪問還是通過代理模式訪問的.從名字還能看出它跟其他代理的一個(gè)不同,就是強(qiáng)制用代理.拿上面普通代理的例子來說,Custom看不到實(shí)體的House的時(shí)候它只能通過代理來訪問,但是由于沒有限制,Custom也可以直接繞過ProxyHouse來訪問House,但是強(qiáng)制代理就多了一個(gè)限制,Custom必須通過ProxyHouse才能訪問House.就像一些房東嫌麻煩,有房客直接電話過來說要看房,房東給出一個(gè)中介的電話說你跟中介聯(lián)系吧.
首先需要在接口里面添加一個(gè)獲取代理的接口
public interface IHouse {
void getHouseInfo();
void signContract();
void payFees();
IHouse getProxy();
}
真實(shí)對(duì)象實(shí)現(xiàn)接口,并在getProxy中實(shí)例化代理,同時(shí)在其他方法里面做代理判斷,只有使用自身自定的代理才會(huì)正常進(jìn)行.
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;
}
}
如果這個(gè)時(shí)候直接操作House對(duì)象,或者通過Custom構(gòu)建的代理來訪問都會(huì)返回以下結(jié)果

所以我們必須使用由真實(shí)對(duì)象指定的代理才可以正常得訪問.
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();
但是這里的強(qiáng)制代理有個(gè)Bug,強(qiáng)制代理其實(shí)并沒有生效,Custom還是可以直接訪問House,例如我通過下面的方式來進(jìn)行訪問,只是通過getProxy創(chuàng)建并獲取代理,但是我不用代理還是直接用House的實(shí)例進(jìn)行訪問,這個(gè)時(shí)候還是可以正常訪問的.后續(xù)會(huì)想辦法解了這個(gè)Bug并且更新上來的.
IHouse house = new House("Downton Abbey", 5000);
house.getProxy();//這里只是通過getProxy創(chuàng)建出代理
Log.i(TAG, "looking for a perfect house");
house.getHouseInfo();
Log.i(TAG, "thinking");
house.signContract();
house.payFees();
4.動(dòng)態(tài)代理
上面介紹的都是自己先寫好的代理類,這樣代理關(guān)系都是固定的,當(dāng)代理多個(gè)真實(shí)對(duì)象的時(shí)候就要寫多個(gè)代理類,并且會(huì)產(chǎn)生冗余的代碼,擴(kuò)展性和可維護(hù)性都不高,而動(dòng)態(tài)代理是基于反射實(shí)現(xiàn)了在程序運(yùn)行的過程中才決定代理什么對(duì)象.像AOP的核心思想就是動(dòng)態(tài)代理.(這里使用的是Java的動(dòng)態(tài)代理)
既然是動(dòng)態(tài)代理就不需要ProxyHouse也不需要實(shí)現(xiàn)IHouse接口了,這里寫一個(gè)ProxyHandler實(shí)現(xiàn)InvocationHandler的invoke接口,并且提供一個(gè)根據(jù)Proxy構(gòu)建出來的代理實(shí)例給Custom.在通過反射調(diào)用真實(shí)對(duì)象具體的方法之前打印出該方法的名字.
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");
從結(jié)果可以看出在真正invoke真實(shí)對(duì)象的方法之前都會(huì)打印出方法名,也可以在這里做一些其他的對(duì)象控制.

這個(gè)時(shí)候整個(gè)過程的時(shí)序圖就變成下面的樣子了,通過JDK的Proxy對(duì)象和反射的機(jī)制來支撐起來動(dòng)態(tài)代理的核心功能.

三.總結(jié)
代理模式的使用場(chǎng)景還是挺多的,可以降低對(duì)象的復(fù)雜度,對(duì)項(xiàng)目進(jìn)行解耦(特別是動(dòng)態(tài)代理的AOP)等,學(xué)習(xí)設(shè)計(jì)模式其實(shí)最適合的方法就是拿來用,在適用于該模式的場(chǎng)景下靈活得去運(yùn)用它才算是真正的掌握一種模式.
相關(guān)文章
Android隱藏標(biāo)題欄及解決啟動(dòng)閃過標(biāo)題的實(shí)例詳解
這篇文章主要介紹了Android隱藏標(biāo)題欄及解決啟動(dòng)閃過標(biāo)題的實(shí)例詳解的相關(guān)資料,這里提供兩種方法幫助大家解決這種問題,需要的朋友可以參考下2017-09-09
Android 仿微信自定義數(shù)字鍵盤的實(shí)現(xiàn)代碼
本篇文章主要介紹了Android 仿微信自定義數(shù)字鍵盤的實(shí)現(xiàn)代碼,具有一定的參考價(jià)值,有興趣的可以了解一下2017-07-07
Android 仿淘寶、京東商品詳情頁向上拖動(dòng)查看圖文詳情控件DEMO詳解
本文給大家介紹android 仿淘寶、京東商品詳情頁向上拖動(dòng)查看圖文詳情控件DEMO詳解,使用兩個(gè)scrollView,兩個(gè)scrollView 豎直排列,通過自定義viewGroup來控制兩個(gè)scrollView的豎直排列,以及滑動(dòng)事件的處理。對(duì)android 拖動(dòng)查看圖文詳情知識(shí)感興趣的朋友一起學(xué)習(xí)吧2016-09-09
Android 手機(jī)獲取手機(jī)號(hào)實(shí)現(xiàn)方法
本文主要介紹Android 獲取手機(jī)號(hào)的實(shí)現(xiàn)方法,這里提供了實(shí)現(xiàn)方法,和具體操作流程,并符實(shí)現(xiàn)代碼,有需要的小伙伴可以參考下2016-09-09
Android7.0以上Uri轉(zhuǎn)路徑的方法實(shí)現(xiàn)(已驗(yàn)證)
這篇文章主要介紹了Android7.0以上Uri轉(zhuǎn)路徑的方法實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03
Android IPC進(jìn)程間通信詳解最新AndroidStudio的AIDL操作)
這篇文章主要介紹了Android IPC進(jìn)程間通信的相關(guān)資料,需要的朋友可以參考下2016-09-09
Android通過PHP服務(wù)器實(shí)現(xiàn)登錄功能
這篇文章主要為大家詳細(xì)介紹了Android通過PHP服務(wù)器實(shí)現(xiàn)登錄功能的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-01-01

