設計模式之代理模式

前言

在我們平時的生活中代理的概念很多,大家都知道,谷歌在國內是被禁止訪問的,但是如果我們使用VPN就可以訪問了。當你想買個新車需要把舊的車賣掉時,一般你不會自己去賣,因爲這很耗費自己的時間,所以你會找二手車交易市場,只需要給人家說出你的要求,具體售賣就不需要你來處理。當你想租房,買房或者賣房一般是會去房產中介。又或者你在外賣平臺訂餐等等。而我們提到的VPN,二手車交易市場,房產中介,外賣平臺就充當了代理的角色。它能夠代替”別人”進行工作,當然有什麼事情直接找代理處理即可,如果代理處理不了纔會找被代理對象處理。
在圖解設計模式中,通過“帶名字的打印機”程序介紹代理模式,當然打印機就是簡單的打印輸出字符信息。照例通過UML類圖瞭解程序的大致框架。

這裏寫圖片描述
首先我們我們給實例賦予名字Alice並顯示該名字,然後將實例名字改爲Bob並顯示。在設置名字時都不會生成Printer類實例(即本人或者說被代理對象),而是有代理處理,當調用代理類的print方法時代理纔會生成被代理對象並調用其print進入實際打印輸出。

主體IPrintable

主體(Subject)角色IPrintable定義了代理(Proxy)角色PrinterProxy和實際的主體(RealSubject)角色Printer之間具有一致性的接口,由於存在IPrintable,所以使用者不必在乎究竟是用的代理PrinterProxy還是實際的主體Printer。

public interface IPrintable {
    public abstract void setPrinterName(String name);
    public abstract String getPrinterName();
    public abstract void print(String string);
}

實際主體Printer

爲了體現生成Printer類需要時間,在初始化時通過heavyJob休眠5秒鐘,由於實現了接口IPrintable,則重寫了設置打印機名字和獲取打印機名字的方法。當然最重要的就是print方法,因爲代理類調用print方法時,需要初始化Printer類,進而調用該類print方法打印內容。

public class Printer  implements IPrintable {
    private String name;

    public Printer() {
        heavyJob("正在生成Printer實例...");
    }

    public Printer(String name) {
        this.name = name;
        heavyJob("正在生成Printer實例("+name+")...");
    }

    @Override
    public void setPrinterName(String name) {
        this.name=name;
    }

    @Override
    public String getPrinterName() {
        return name;
    }

    @Override
    public void print(String string) {
        System.out.println("======== "+name+" ========");
        System.out.println(string);
    }

    private void heavyJob(String msg){
        System.out.print(msg);
        for (int i = 0; i <5 ; i++) {
            try {
                Thread.sleep(1000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
        System.out.println("生成結束");
    }
}

代理人PrinterProxy

在該設計模式代理人不可或缺,它有兩個變量一個是打印機名字,一個是打印機真實的對象real。對於設置打印機名字,會先判斷真實的打印機對象real是否爲空,如果不爲空,表示已經存在真實對象則給真實的打印機對象設置名字,否則只會設置代理的名字。而print方法打印內容,也就是在該方法中我們才初始化真實打印機real,並調用最終的打印內容的方法print.當然已經初始化了就不會再次進行初始化。需要注意的是Printer並不知道PrinterProxy的存在,但是它們是緊密的聯繫在一起的。

public class PrinterProxy implements IPrintable{
    private String name;
    private Printer real;
    public PrinterProxy() {
    }

    public PrinterProxy(String name) {
        this.name = name;
    }

    @Override
    public synchronized  void setPrinterName(String name) {
        if (real!=null){
          real.setPrinterName(name);
        }
        this.name=name;
    }

    @Override
    public  String getPrinterName() {
        return name;
    }

    @Override
    public void print(String string) {
        realize();
        real.print(string);
    }

    private synchronized  void realize() {
        if (real==null){
            real=new Printer(name);
        }
    }
}

在代理類中我們給設置打印機名字和初始化真實打印機對象方法加了同步關鍵字synchronized,如果不使用當多個線程分別調用setPrintName和realize方法時可能會導致PrinterProxy類的名字和Printer類的名字不同的情況。

測試類

public class Main {
    public static void main(String [] args){
        IPrintable iPrintable=new PrinterProxy("Alice");
        System.out.println("現在的名字是"+iPrintable.getPrinterName());
        iPrintable.setPrinterName("Bob");
        System.out.println("現在的名字是"+iPrintable.getPrinterName());
        iPrintable.print("Hello ,world");
    }
}

由於PrinterProxy和Printer類都實現了IPrintable,因此測試類使用時不用在意究竟是調用多了代理PrinterProxy還是Printer。在複雜的業務中,我們可以在代理中指定哪些問題是它可以解決的,當遇到它不能解決的問題時,纔去轉交給本人去解決。

動態代理實現

在上面的介紹中,我們使用的是靜態代理的方式來介紹代理模式,還有一種是動態代理,代理類是不存在的,而是動態生成的,那麼我們該如何將上面示例的靜態代理用動態代理實現呢,其實也不難,我們只需要瞭解Proxy類和InvocationHandler接口就可以實現。
它的實現可分爲三步 ①,創建被代理類②,實現InvocationHandler接口,這是負責連接代理類和委託類的中間類必須實現的接口③,通過Proxy類新建代理類對象。在上述示例代碼我們已經創建了被代理對象Printer,接下來創建一個實現InvocationHandler接口的類,爲了在客戶端更方便使用代理,我們直接在這個實現類中寫一個創建代理的方法。代碼如下

public class DynamicHandler implements InvocationHandler {
    // 目標對象
    private Object targetObject;

    //創建代理對象 這段也可以不在此類,也可以放在客戶端裏面
    public Object createProxy(Object targetOjbect) {
        this.targetObject = targetOjbect;
        /**
         * 創建代理對象
         * Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
         * loader:代理類的類加載器
         * interfaces:指定代理類所實現的接口
         * h:動態代理對象在調用方法的時候,關聯的InvocationHandler對象
         */
        return Proxy.newProxyInstance(targetOjbect.getClass().getClassLoader(),
                targetOjbect.getClass().getInterfaces(), this);
    }

    /**
     * InvocationHandler接口所定義的唯一的一個方法,該方法負責集中處理動態代理類上的所有方法的調用。
     * 調用處理器根據這三個參數進行預處理或分派到委託類實例上執行
     * @param proxy  代理類的實例
     * @param method  代理類被調用的方法
     * @param args   調用方法的參數
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //觸發真實對象之前或者之後可以做一些額外操作
        Object result = null;
        System.out.println("method:"+method.getName()+"proxy:"+proxy.getClass().getName());
        System.out.println("Before invoke ...");
        result = method.invoke(this.targetObject, args);//通過反射執行某個類的某方法
        System.out.println("After invoke ...");
        return result;
    }
}

然後我們將測試類做下更改,使用DynamicHandler創建代理類對象

public class Main {
    public static void main(String [] args){
        IPrintable iPrintable;
        DynamicHandler dynamicHandler=new DynamicHandler();
        iPrintable = (IPrintable) dynamicHandler.createProxy(new Printer("Alice"));
        System.out.println("現在的名字是"+iPrintable.getPrinterName());
        iPrintable.setPrinterName("Bob");
        System.out.println("現在的名字是"+iPrintable.getPrinterName());
        iPrintable.print("Hello ,world");
    }
}

通過上面的介紹你或許應該發現動態代理比靜態代理使用方便多了。如果是靜態代理,我們想要在接口中增加方法,需要修改我們很多代碼,但是動態代理不需我們手動的去編寫代理類,而是通過反射機制動態生成的,而使用動態代理就簡單了許多,這樣不僅簡化了我們的工作也提高了系統的擴展性。好了,關於代理就介紹這麼多,水平有限若有問題請指出,Hava a wonderful day.

如需文章中所寫代碼,請移步GitHub查看

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