代理模式概述
給實際對象生成一個代理對象,將代理對象交給用戶,避免用戶操作實際對象。
可以做到不修改實際對象的代碼,而爲實際對象的方法添加新功能。比如在方法前後打印時間可以知道該方法的執行耗時。
靜態代理
在編譯期間就已經確定了代理對象和實際對象之間的關係。代理對象是在編譯期間就生成的。
例子
小明想買個美版的mac,但是他沒渠道,所以只能找一個代購去幫忙購買。
解析
-
要執行的動作是購買mac
-
實際對象是小明
-
代理對象是代購
代碼實現
首先,聲明一個動作購買mac。
public interface Action {
void buyMac();
}
其次,創建一個實際對象小明,並讓其實現上面的購買mac動作。
public class XiaoMing implements Action {
@Override
public void buyMac() {
System.out.println("小明購買mac");
}
}
然後,創建一個代理對象代購,也讓其實現上面的購買mac動作。
public class MyProxy implements Action {
private Action target;
public MyProxy(Action target) {
// 構造函數需要告訴代購,是誰需要代購
this.target = target;
}
@Override
public void buyMac() {
target.buyMac();
}
}
最後,實際使用。
MyProxy myProxy = new MyProxy(new XiaoMing());
myProxy.buyMac();
使用代理模式的原因
上面的代碼看上去似乎有點多餘,明明可以只創建一個小明,然後裏面在聲明一個購買mac的方法就可以,爲什麼還要多此一舉的聲明一個接口,創建一個代理對象呢?
假如現在新增一個需求:需要記錄購買了多少臺mac,如果超過了10臺,那麼就不繼續代購了,因爲容易被查水錶/狗頭。
那該怎麼實現呢?假如沒有使用代理的方式,那麼代碼可能是這樣的:
首先,創建一個Accountant類,用於管理數量。
public class Accountant {
// 記錄購買的mac數量
private static int macCount = 0;
/**
* 購買mac數量+1
*/
public static void addNewMac(){
macCount++;
}
/**
* 獲取當前購買了多少臺mac
* @return int
*/
public static int getMacCount(){
return macCount;
}
}
然後,在每次購買mac的時候,都調用Account
類的方法進行判斷。
public class XiaoMing implements Action {
@Override
public void buyMac() {
if (Accountant.getMacCount() >= 10){
System.out.println("已購買數量超標");
}else {
System.out.println("小明購買mac");
Accountant.addNewMac();// 已購mac數量+1
}
}
}
這樣看上去很美好不是嗎?
假如此時又有一個小紅需要購買mac,會再創建一個XiaoHong
。
public class XiaoHong implements Action {
@Override
public void buyMac() {
if (Accountant.getMacCount() >= 10){
System.out.println("已購買數量超標");
}else {
System.out.println("小紅購買mac");
Accountant.addNewMac();// 已購mac數量+1
}
}
}
假如此時小黑、小白、小蘭也想購買mac呢?怎麼辦?繼續創建XiaoHei
、XiaoBai
、XiaoLan
嗎?
這樣,每次在購買mac之前都需要寫一次判斷方法,太傻了。程序員最擅長什麼?
把重複的動作變成自動的。
這個時候,使用代理模式來處理的話,只需要在代理對象中寫一次判斷就可以了。
public class MyProxy implements Action {
private Action target;
public MyProxy(Action target) {
// 構造函數需要告訴代購,是誰需要代購
this.target = target;
}
@Override
public void buyMac() {
if (Accountant.getMacCount() >= 10){
System.out.println("已購買數量超標");
}else {
target.buyMac();
Accountant.addNewMac();// 已購mac數量+1
}
}
}
看啊,人類的智慧!理論上少寫了無數行重複代碼!太厲害了!奧利給!
動態代理
在運行時,才確立代理對象和實際對象間的關係。代理對象是在運行時生成的。
例子
現在這個代購生意做大了,不但能代購mac了,還能代購手機、奶粉、球鞋等你能想到的一切東西。而且爲了炫耀,他每次接到單都要發朋友圈。
解析
-
要執行的動作是代購手機、奶粉、球鞋等
-
實際對象會存在很多個
-
每次執行動作前都要發朋友圈
代碼實現
創建若干個接口,分別有購買手機、購買奶粉、購買球鞋等。(這裏爲了減少代碼量,只寫了一個購買手機的例子)
public interface Action1 {
void bugPhone();
}
同樣,創建實際對象(要購買手機的人),並實現上面的接口。
public class XiaoHei implements Action1 {
@Override
public void bugPhone() {
System.out.println("小黑購買phone");
}
}
現在就存在問題了,如果還像使用靜態代理一樣的方式去創建一個代理對象的話,那麼類文件數量肯定會非常誇張。那有沒有什麼辦法可以不創建類文件,但是又能生成類呢?人類的智慧是無窮的,動態代理就出來了!
// 1.實例化實際對象
final XiaoHei xiaoHei = new XiaoHei();
// 2.獲取類加載器
ClassLoader loader = xiaoHei.getClass().getClassLoader();
// 3.獲取所有接口class,這裏的XiaoHei只實現了Action1.class
Class[] interfaces = xiaoHei.getClass().getInterfaces();
// 4.實例化出代理對象
Action1 proxy =(Action1) Proxy.newProxyInstance(loader, interfaces, new InvocationHandler() {
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
// 代理對象每次執行方法時,都會調用到這裏
broadcast();// 發朋友圈
return method.invoke(xiaoHei,objects);// 執行實際對象的方法
}
});
// 5.執行代理對象的方法
proxy.bugPhone();
使用動態代理的原因
通過上面的例子,可以看到,不需要再創建代理類文件了,直接在代碼中生成了代理類,顯著的減少了文件數量。