## 故事
周末放假,小孫睡到12點才告別周公醒來,頓時饑腸轆轆。舍長小王正準備去食堂買飯,作為一個好舍長小王主動要幫小孫帶飯。小孫點了米飯、宮保雞丁、芬達。小孫起床洗漱,然后靜待舍長。小孫心理尋思道舍長果然是好舍長啊。下面我們先把這個故事抽象一下,畫作類圖。這個類圖即代理模式。

## 代理模式
定義:為其他對象提供一種代理以控制對這個對象的訪問。怎么理解這句話呢?從生活的角度來說就是:用一個對象A代替另一個對象B來處理本來應該由對象B完成的事情,例如上面買飯的例子,又如通過黃牛買票等等。從代碼的角度來說則是:用一個對象A來過濾、預處理對對象B的調用 。不管從哪個角度看吧,代理都需要擁有和實際對象的需要被代理的全部方法。
## 靜態代理
類圖如上所示了,定義也看了。接下來我們按照類圖來看看代碼。
###接口(宿舍成員)
~~~
/**
* 宿舍成員接口
* 開發時間:2014-8-12 下午9:17:19
*/
public interface DormitoryMember {
//買主食
public boolean buyStapleFood(String stapleFoodName);
//買菜
public boolean buyDish(String dishName);
//買飲料
public boolean buyDrink(String drinkName);
}
~~~
###代理(宿舍長)
~~~
//代理類
public class DormitoryMemberProxy implements DormitoryMember {
//持有一個被代理的對象
private DormitoryMember dormitoryMember;
//構造時傳入被代理對象
public DormitoryMemberProxy(DormitoryMember dormitoryMember){
this.dormitoryMember=dormitoryMember;
}
//代理買主食
public boolean buyStapleFood(String stapleFoodName) {
return dormitoryMember.buyStapleFood(stapleFoodName);
}
//代理買菜
public boolean buyDish(String dishName) {
return dormitoryMember.buyDish(dishName);
}
//代理買飲料
public boolean buyDrink(String drinkName) {
return dormitoryMember.buyDrink(drinkName);
}
}
~~~
###實際類(小孫)
~~~
//實際的類
public class DormitoryMemberImpl implements DormitoryMember {
//買主食
public boolean buyStapleFood(String stapleFoodName) {
try{
System.out.println("買到主食" + stapleFoodName);
}catch(Exception e){
return false;
}
return true;
}
//買菜
public boolean buyDish(String dishName) {
try{
System.out.println("買到菜:" + dishName);
}catch(Exception e){
return false;
}
return true;
}
//買飲料
public boolean buyDrink(String drinkName) {
try{
System.out.println("買到飲料:" + drinkName);
}catch(Exception e){
return false;
}
return true;
}
}
~~~
## 故事繼續
宿舍長去到食堂買飯,結果小孫要的芬達斷貨了。于是宿舍長久提著米飯和菜就回去了,小孫吃著米飯和菜沒有飲料難以下咽。但是,也不好說宿舍長,舍長是好舍長啊。將就的吃了午飯。時間來的晚飯時間,小孫在英雄聯盟里正殺的起勁,那又閑工夫去買飯于是這工作有落到了好舍長身上。這次小孫學聰明了交代舍長說,如果有他點的東西沒有了就打個電話回來在決定買什么替代。
###問題來了
這里需求已經改變了,如果小孫要的東西沒有了的話,要給小孫打電話以決定是不是買其他的或者不買。這個電話自然是宿舍長來打。所以我們要對代理類進行修改!這也就是使用代理模式的好處,我們關閉了對實際類的修改。代理類修改后的代碼如下:
~~~
//代理類
public class DormitoryMemberProxy implements DormitoryMember {
//持有一個被代理的對象
private DormitoryMember dormitoryMember;
//構造時傳入被代理對象
public DormitoryMemberProxy(DormitoryMember dormitoryMember){
this.dormitoryMember=dormitoryMember;
}
//代理買主食
public boolean buyStapleFood(String stapleFoodName) {
boolean buySuccess=dormitoryMember.buyStapleFood(stapleFoodName);
if( buySuccess=false){
System.out.println("給小孫打電話");
return false;
}else{
return true;
}
}
//代理買菜
public boolean buyDish(String dishName) {
boolean buySuccess=dormitoryMember.buyDish(dishName);
if( buySuccess=false){
System.out.println("給小孫打電話");
return false;
}else{
return true;
}
}
//代理買飲料
public boolean buyDrink(String drinkName) {
boolean buySuccess=dormitoryMember.buyDrink(drinkName);;
if( buySuccess=false){
System.out.println("給小孫打電話");
return false;
}else{
return true;
}
}
}
~~~
###問題(1)
看代碼可以知道,我們要對代理類中的每一個方法都進行同樣的修改。極限假設我們實現的這個接口有1000個方法,哭死都不冤枉。
### 問題(2)
極限假設,偉大的宿舍長不止幫小孫買飯,還要幫小李買衣服,還要幫小趙買電腦。換句話說宿舍長要代理不同的接口。但是這是不可能實現的,因為在代理類所持有的實際類是確定的,也就是說每一個代理類只能代理一個借口。
顯然很不科學,這么多的代碼重復出現。有不科學的情況出現,自然就會有辦法因對。最怕的是沒有發現問題,沒有問題就沒有改進。下面我們上動態代理。
## 動態代理
首先看問題一,既然當代理調用實際類時在代理類中的預處理都是一樣的那么我們要達到代碼不重復的話就只要保證調用實際類之前或者之后都回掉到某個處理就好了。當然以前好像沒有學過可以對很多種不同的方法都同樣回掉一個函數,因為類的參數個數和類型以及返回值都是不一致的。因此,java中就為此專門有一個接口可以實現這樣的功能,InvocationHandler接口。
再看問題二,對于問題二我們必須將代理類和被代理類解耦,通常我們解決這類問題就是用反射。這也不例外,這里java提供的反射機制可以在運行時產生代理類,即動態代理。這篇博客已經太長了,分開來吧。先把動態代理的代碼貼上。其中實際類和接口沒有變化!
### 代理類工廠
~~~
import java.lang.reflect.Proxy;
//代理工廠類
public class ProxyFactory {
//實際類
private Object target;
public ProxyFactory(Object target){
this.target=target;
}
//根據傳入的實際類生成代理
public Object getInstance(){
//實例化一個繼承了InvocationHandler接口的預處理對象
ProxyHandler handler=new ProxyHandler(target);
//反射得到代理類
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
handler);//handler即集中處理在動態代理類對象上的方法調用
}
}
~~~
###繼承了InvocationHandler接口的handler類
~~~
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class ProxyHandler implements InvocationHandler {
private Object target;
public ProxyHandler(Object target){
this.target=target;
}
//所有代理方法的公共處理方法
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object result =null;
result=method.invoke(target, args);
//要買的東西斷貨時
if(result.toString().equals("false")){
System.out.println("給小孫打電話");
return result;
}else{
return result;
}
}
}
~~~
### 修改后的客戶端調用
~~~
public class Client {
public static void main(String[] args){
//真正得到的代理類在客戶端強轉
DormitoryMember dormitoryMember=(DormitoryMember)new ProxyFactory(new DormitoryMemberImpl()).getInstance();
dormitoryMember.buyStapleFood("米飯");
}
}
~~~
**總結:**動態代理通過反射把代理類和實際類之間的耦合解開了,同時通過繼承了InvocationHandler接口的handler類對代理的方法進行統一的處理。也就解決靜態代理所遇到的問題。