1. 程式人生 > 其它 >23種設計模式:代理模式

23種設計模式:代理模式

代理模式 (Proxy Pattern)

1、介紹

在某些情況下,客戶不能或者不想直接訪問另一個物件,這時需要一個中介來幫忙完成任務。

  • 客戶:訪問物件;
  • 中介:代理;
  • 被訪問物件:真實主題(目標物件);

例如:

  • 買高鐵票不一定要去高鐵站,可以通過12306網站或者其他車票代售點買;

  • 找工作、找房子等也可以通過找中介完成。

1.1、定義

訪問物件不適合或者不能直接引用目標物件,此時給目標物件提供一個代理以控制對該物件的訪問。代理物件作為訪問物件和目標物件之間的中介。

根據代理的建立時期,代理模式分為靜態代理和動態代理。

  • 靜態:程式執行前。程式設計師在建立目標物件時建立代理類;
  • 動態
    :程式執行時。運用反射機制動態建立。

1.2、應用場景

使用代理模式主要有兩個目的:保護增強目標物件。

應用場景 說明 舉例
遠端代理 隱藏目標物件存在於不同地址空間的事實,方便客戶端訪問 使用者申請網盤空間時,會在系統中建立一個虛擬硬碟,使用者通過虛擬硬碟實際訪問的是網盤空間。
虛擬代理 建立目標物件所需的開銷大 下載很大的檔案需要很長時間,此時可以先用小比例的虛擬代理替換真實的物件,消除使用者對伺服器慢的感覺。
安全代理 控制不同級別客戶對真實物件的訪問許可權
智慧指引 呼叫目標物件時,附加一些額外的處理功能 增加計算真實物件的引用次數的功能,這樣當該物件沒有被引用時,就可以自動釋放它。
延遲載入 為了提高系統的效能,延遲對目標的載入

2、靜態代理

2.1、特點

優點:

  • 在客戶端與目標物件之間,起到一箇中介作用和保護目標物件的作用;
  • 可以擴充套件目標物件的功能;
  • 將客戶端與目標物件分離,在一定程度上降低了系統的耦合度,增加了程式的可擴充套件性。

缺點:

  • 會造成系統設計中類的數量增加:增加真實主題就要增加代理,程式碼量翻倍;
  • 設計代理之前,必須先有真實主題(目標物件),不靈活;
  • 在客戶端和目標物件之間增加一個代理物件,會造成請求處理速度變慢。

使用動態代理方式,即可解決以上的缺點。

2.2、結構及實現

  1. 抽象主題類(Subject:定義一個介面或抽象類,宣告要實現的業務方法;
  2. 真實主題類(Real Subject:實現了抽象主題中的具體業務,是代理物件引用的真實物件;
  3. 代理類(Proxy:實現了抽象主題類,提供一個介面來引用真實主題,可以訪問、控制和擴充套件真實主題的功能;
  4. 客戶端(Client:通過代理類訪問真實主題類的業務方法;

結構圖

結構圖實現:

Subject

public interface Subject {
    /**
     * 業務方法
     */
    void request();
}

RealSubject

public class RealSubject implements Subject{
    @Override
    public void request() {
        System.out.println("真實物件:訪問真實主題的業務方法...");
    }
}

Proxy

public class Proxy implements Subject {
    /**
     * 引用真實主題
     */
    private Subject realSubject;

    @Override
    public void request() {
        setRealSubject();
        preRequest();
        realSubject.request();
        postRequest();
    }

    private void setRealSubject() {
        if (realSubject == null) {
            this.realSubject = new RealSubject();
        }
    }

    private void preRequest() {
        System.out.println("代理物件:訪問真實主題前的預處理...");
    }

    private void postRequest() {
        System.out.println("代理物件:訪問真實主題後的後續處理...");
    }

}

Client

public class Client {
    public static void main(String[] args) {
        Proxy proxy = new Proxy();
        proxy.request();
    }
}

執行結果

2.3、應用例項

通過房屋中介租房子。

Rent:租房業務

public interface Rent {
    /**
     * 租房業務
     */
    void rent();
}

Landlord:房東

public class Landlord implements Rent {

    @Override
    public void rent() {
        System.out.println("房東:出租房子");
    }
}

Proxy:房屋中介

public class Proxy implements Rent {

    private Landlord landlord;

    @Override
    public void rent() {
        setLandlord();
        visit();
        landlord.rent();
        pay();
    }

    private void pay() {
        System.out.println("中介:收取成交後的中介費");
    }

    private void visit() {
        System.out.println("中介:帶你看房子");
    }

    private void setLandlord() {
        if (landlord == null) {
            landlord = new Landlord();
        }
    }
}

Client

public class Client {
    public static void main(String[] args) {
        Proxy proxy = new Proxy();
        proxy.rent();
    }
}

執行結果

3、動態代理

動態代理也叫 JDK 代理、介面代理,相對靜態代理比較靈活。

3.1、特點

優點

  1. 不需要實現介面;
  2. 通過反射機制,動態建立代理物件;
  3. 能在程式碼執行時動態地改變某個物件的代理,併為代理物件動態地增加方法和行為。

解決靜態代理的缺點:

  1. 會造成系統設計中類的數量增加:增加真實主題就要增加代理,程式碼量翻倍;
  2. 設計代理之前,必須先有真實主題;
  3. 在客戶端和目標物件之間增加一個代理物件,會造成請求處理速度變慢。

3.2、結構及實現

與靜態代理相同:

  1. 抽象主題類(Subject:定義一個介面或抽象類,宣告要實現的業務方法;
  2. 真實主題類(Real Subject:實現了抽象主題中的具體業務,是代理物件引用的真實物件;
  3. 代理類(Proxy:實現了抽象主題類,提供一個介面來引用真實主題,可以訪問、控制和擴充套件真實主題的功能;
  4. 客戶端(Client:通過代理類訪問真實主題類的業務方法;

結構圖:

結構圖實現:

Subject

public interface Subject {
    /**
     * 業務方法
     */
    void request();
}

RealSubject

public class RealSubject1 implements Subject{
    @Override
    public void request() {
        System.out.println("真實物件1:訪問真實主題的業務方法...");
    }
}

public class RealSubject2 implements Subject{
    @Override
    public void request() {
        System.out.println("真實物件2:訪問真實主題的業務方法...");
    }
}

DynamicProxy

public class DynamicProxy implements InvocationHandler {
    /**
     * 真實主題
     */
    private Object object;

    /**
     * 設定真實主題
     *
     * @param object 真實主題
     */
    public void setObject(Object object) {
        this.object = object;
    }

    /**
     * 獲取代理物件
     *
     * @return 真實主題的代理物件
     */
    public Object getProxy() {
        return Proxy.newProxyInstance(object.getClass().getClassLoader(),
                object.getClass().getInterfaces(),
                this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        preRequest();
        // 執行真實物件的方法
        Object result = method.invoke(object, args);
        postRequest();
        return result;
    }


    private void preRequest() {
        System.out.println("代理:預處理");
    }

    private void postRequest() {
        System.out.println("代理:後續處理");
    }
}

Client

public class Client {
    public static void main(String[] args) {
        // 獲取處理器
        DynamicProxy handler = new DynamicProxy();
        // 設定真實主題
        handler.setObject(new RealSubject1());

        // 獲取代理物件
        Subject proxy = (Subject) handler.getProxy();
        //
        proxy.request();
    }
}

執行結果

3.3、應用例項

在DAO層,增加日誌功能。

UserDao:相當於Subject

public interface UserDao {
    /**
     * 新增使用者
     */
    void insertUser();

    /**
     * 刪除使用者
     */
    void deleteUser();

    /**
     * 更新使用者
     */
    void updateUser();

    /**
     * 查詢使用者
     */
    void getUser();
}

UserDaoImpl:相當於RealSubject

public class UserDaoImpl implements UserDao {
    @Override
    public void insertUser() {
        System.out.println("新增使用者...");
    }

    @Override
    public void deleteUser() {
        System.out.println("刪除使用者...");
    }

    @Override
    public void updateUser() {
        System.out.println("更新使用者...");
    }

    @Override
    public void getUser() {
        System.out.println("查詢使用者...");
    }
}

DynamicProxy

public class DynamicProxy implements InvocationHandler {
    /**
     * 真實主題
     */
    private Object object;

    /**
     * 設定真實主題
     *
     * @param object 真實主題
     */
    public void setObject(Object object) {
        this.object = object;
    }

    /**
     * 獲取代理物件
     *
     * @return 真實主題的代理物件
     */
    public Object getProxy() {
        return Proxy.newProxyInstance(object.getClass().getClassLoader(),
                object.getClass().getInterfaces(),
                this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        log(method.getName());

        return method.invoke(object, args);
    }

    /**
     * 日誌功能
     *
     * @param method 方法,通過反射獲取方法名
     */
    private void log(String method) {
        System.out.println("LOG:執行了" + method + "方法");
    }
}

Client

public class Client {
    public static void main(String[] args) {

        DynamicProxy dynamicProxy = new DynamicProxy();

        dynamicProxy.setObject(new UserDaoImpl());

        UserDao proxy = (UserDao) dynamicProxy.getProxy();

        proxy.insertUser();
        proxy.deleteUser();
        proxy.updateUser();
        proxy.getUser();
    }
}

執行結果

4、小結

  1. 代理物件作為訪問物件和目標物件之間的中介,控制對目標物件的訪問;
  2. 靜態代理在程式執行前手動建立,動態代理在程式執行時由反射機制動態建立;
  3. 代理模式可以起到中介和保護作用,擴充套件和增強目標物件的功能;
  4. 靜態代理不靈活,需要先有真實主題才能設計代理;代理與真實主題一一對應,即有一個主題就要有一個代理,增加程式碼量;
  5. 動態代理非常靈活,只需要一個動態代理類,不用隨著真實主題的增加而增加。