代理模式
為實(shí)際要訪問(wèn)的對(duì)象創(chuàng)建一個(gè)代理怖糊,客戶不再直接訪問(wèn)原始對(duì)象帅容,而是通過(guò)代理對(duì)象,間接訪問(wèn)原始對(duì)象伍伤,這樣就可以控制客戶對(duì)原始對(duì)象對(duì)訪問(wèn)并徘,或者在訪問(wèn)原始對(duì)象前增加預(yù)處理等。代理模式常用的場(chǎng)景包括:
功能增強(qiáng)扰魂,在不修改原始對(duì)象接口的前提下增加新的功能麦乞。例如原先有一個(gè)FileOperator類,負(fù)責(zé)各種文件操作(打開(kāi)劝评,關(guān)閉姐直,刪除等等), 如果現(xiàn)在需要對(duì)于每個(gè)操作加上日志,怎么實(shí)現(xiàn)呢? 最簡(jiǎn)單的方法當(dāng)然時(shí)直接修改FileOperator類蒋畜,對(duì)每個(gè)操作加上日志声畏,但是這種的缺點(diǎn)是:
(1) 干擾FileOperator核心功能,F(xiàn)ileOperator類添加了大量非核心代碼姻成,導(dǎo)致代碼混亂插龄,如果后續(xù)由大量額外功能,F(xiàn)ileOperator會(huì)非秤犊剩混亂辫狼,并且沒(méi)法隔離不同的額外需求,例如有兩個(gè)功能:(1)將log紀(jì)錄到磁盤(pán)辛润,(2)將log上傳到服務(wù)器, 如果這兩個(gè)功能只能2選1膨处,那就非常麻煩了
(2) 如果FileObserver來(lái)自于第三方庫(kù)见秤,可能無(wú)法修改
(3) FileObserver來(lái)自于第三方,且可修改源碼真椿,這種情況如果將來(lái)需要合并新的代碼鹃答,可能會(huì)出現(xiàn)大量到?jīng)_突
因此通常都不會(huì)直接修改FileOperator類,而是通過(guò)代理的方式實(shí)現(xiàn)突硝,創(chuàng)建一個(gè)代理類FileOperatorProxy(靜態(tài)代理)测摔,并封裝FileOprator類。這種方式非常像裝飾者模式解恰,主要區(qū)別在于锋八,裝飾者模式會(huì)定義統(tǒng)一的接口,裝飾者必須實(shí)現(xiàn)被裝飾者的所有接口护盈,而靜態(tài)代理只需要定義需要的接口挟纱,其他不需要訪問(wèn)的接口可以省略。遠(yuǎn)程代理
主要用于客戶無(wú)法直接訪問(wèn)原始對(duì)象的情況腐宋,需要通過(guò)Proxy實(shí)現(xiàn)與遠(yuǎn)程對(duì)象的交互紊服,例如Binder機(jī)制,客戶端直接訪問(wèn)BinderProxy的接口胸竞,proxy通過(guò)Binder驅(qū)動(dòng)與遠(yuǎn)端Binder對(duì)象進(jìn)行交互欺嗤。
靜態(tài)代理
通過(guò)手動(dòng)創(chuàng)建一個(gè)Proxy類,并且封裝目標(biāo)類卫枝,例如上述FileOperatorProxy煎饼。所有對(duì)FileOperator的訪問(wèn)都需要通過(guò)FileOpratorProxy預(yù)處理。靜態(tài)代理最大的缺點(diǎn)在于對(duì)原始類對(duì)訪問(wèn)能力局限于Proxy類剃盾,如果想暴露原始類的所有接口腺占,Proxy類就得定義原始類的所有接口的訪問(wèn)入口淤袜,如果接口很多痒谴,并且很多接口實(shí)際上是不需要預(yù)處理,則Proxy 類會(huì)寫(xiě)很多無(wú)用代碼, 例如
public class FileOperator {
public void writeToFile() {
//write content to file
}
public void getOperateCount() {
//get operate count
}
}
實(shí)現(xiàn)一個(gè)Proxy 類铡羡,紀(jì)錄log
public class FileOperatorProxy {
private FileOperator target;
public FileOperatorProxy(FileOperator target) {
this.target = target;
}
public void writeToFile() {
//print log
target.writeToFile();
}
public int getOperateCount() {
return target.getOperateCount();
}
}
顯示积蔚,F(xiàn)ileOperatorProxy類的getOperateCount這個(gè)方法就很累贅,但是為了暴露FileOperator的getOperateCount方法烦周,Proxy 類必須實(shí)現(xiàn)大量這樣的無(wú)用接口尽爆,非常繁瑣。這就有必要用到動(dòng)態(tài)代理了读慎。
動(dòng)態(tài)代理
于靜態(tài)代理的區(qū)別在于漱贱,靜態(tài)代理類必須預(yù)先創(chuàng)建,而動(dòng)態(tài)代理類是運(yùn)行時(shí)根據(jù)原始類的接口(注意夭委,必須是interface, 否則會(huì)拋異常)動(dòng)態(tài)創(chuàng)建Proxy, 通過(guò)InvocationHandler接口幅狮,所有對(duì)Proxy對(duì)象對(duì)訪問(wèn)都統(tǒng)一調(diào)用InvocationHandler的invoke接口,這樣,所有的增強(qiáng)操作都可以在invoke中完成崇摄,不需要增強(qiáng)的方法擎值,直接調(diào)用原始對(duì)象的方法,因此動(dòng)態(tài)代理的關(guān)鍵就在于實(shí)現(xiàn)InvocationHandler的invoke對(duì)象逐抑,對(duì)特定方法進(jìn)行處理鸠儿,其他方法不做任何處理:
public interface IFileOperator {
void writeToFile();
int getOperateCount();
}
public class FileOperator implements IFileOperator {
@Override
public void writeToFile() {
//writeToFile
}
@Override
public int getOperateCount() {
//return operate count
}
}
public class MyInvocationHandler implements InvocationHandler {
FileOperator operator;
public MyInvocationHandler(FileOperator operator) {
this.operator = operator;
}
@Override
public void invoke(Object proxy, Method method, Object[] args) {
if (method.getName().equals("writeToFile")) {
//print log
operator.writeToFile();
} else {
method.invoke(operator, args);
}
}
public IFileOperator createProxy(IFileOperator operator) {
IFileOperator proxy = (IFileOperator)
Proxy.newInstance(ClassLoader.getSystemClassLoader(), new
Class[] {IFileOperator.class}, new MyInvocationHandler(operator);
return proxy;
}
可以看到,只需要對(duì)特定的方法做出來(lái)就行了厕氨,處于方法不做任何處理进每,就可以暴露原始對(duì)象的所有方法,避免了大量無(wú)用的重復(fù)方法, 這就是動(dòng)態(tài)代理的優(yōu)勢(shì)命斧。這里可能會(huì)有一個(gè)擔(dān)心:每次創(chuàng)建代理的時(shí)候都需要?jiǎng)討B(tài)創(chuàng)建一個(gè)Proxy Class品追,會(huì)不會(huì)有很多開(kāi)銷(xiāo)?實(shí)際上是不用擔(dān)心的冯丙,Proxy.newInstance方法首先根據(jù)傳入的interface列表創(chuàng)建Class肉瓦,創(chuàng)建前會(huì)先檢查對(duì)應(yīng)的列表是否已經(jīng)創(chuàng)建過(guò)Proxyclass,如果已創(chuàng)建胃惜,則復(fù)用泞莉,這樣實(shí)際上每次調(diào)用Proxy.newInstance也只是創(chuàng)建了一個(gè)Proxy對(duì)象而已,開(kāi)銷(xiāo)跟靜態(tài)代理是一樣的