【Java】小例子巧妙理解代理模式

代理模式概述

給實際對象生成一個代理對象,將代理對象交給用戶,避免用戶操作實際對象。

可以做到不修改實際對象的代碼,而爲實際對象的方法添加新功能。比如在方法前後打印時間可以知道該方法的執行耗時。

靜態代理

在編譯期間就已經確定了代理對象和實際對象之間的關係。代理對象是在編譯期間就生成的。

例子

小明想買個美版的mac,但是他沒渠道,所以只能找一個代購去幫忙購買。

解析

  1. 要執行的動作是購買mac

  2. 實際對象是小明

  3. 代理對象是代購

代碼實現

首先,聲明一個動作購買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呢?怎麼辦?繼續創建XiaoHeiXiaoBaiXiaoLan嗎?

這樣,每次在購買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了,還能代購手機、奶粉、球鞋等你能想到的一切東西。而且爲了炫耀,他每次接到單都要發朋友圈。

解析

  1. 要執行的動作是代購手機、奶粉、球鞋等

  2. 實際對象會存在很多個

  3. 每次執行動作前都要發朋友圈

代碼實現

創建若干個接口,分別有購買手機、購買奶粉、購買球鞋等。(這裏爲了減少代碼量,只寫了一個購買手機的例子)

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();

使用動態代理的原因

通過上面的例子,可以看到,不需要再創建代理類文件了,直接在代碼中生成了代理類,顯著的減少了文件數量。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章