Java設計模式-代理模式

2023-05-09 12:01:59

簡介

代理模式是一種結構型設計模式,它可以讓我們通過一個代理物件來存取一個真實的目標物件,從而實現對目標物件的功能擴充套件或保護。代理模式的主要角色有三個:

  • 抽象主題(Subject):定義了真實主題和代理主題的公共介面,使得在任何使用真實主題的地方都可以使用代理主題。
  • 真實主題(RealSubject):實現了抽象主題的介面,定義了真實的業務邏輯,是代理主題所代表的真實物件。
  • 代理主題(Proxy):也實現了抽象主題的介面,但是在呼叫真實主題的方法之前或之後,可以執行一些額外的操作,從而對真實主題進行控制或增強。

代理模式可以幫助我們解決以下幾種問題:

  • 當我們無法或不想直接存取一個物件時,可以通過一個代理物件來間接存取,例如遠端代理、虛擬代理等。
  • 當我們想要給一個物件提供額外的功能時,可以通過一個代理物件來實現,而不需要修改原有的物件,例如快取代理、紀錄檔代理等。
  • 當我們想要給一個物件增加一些存取控制或安全保護時,可以通過一個代理物件來實現,例如防火牆代理、許可權代理等。

實現

根據代理模式的定義,我們可以用以下的類圖來表示它的結構:

其中,Client是使用者端類,它需要使用Subject介面提供的方法。Proxy是代理類,它持有一個RealSubject的參照,並且實現了Subject介面。RealSubject是真實類,它也實現了Subject介面,並且定義了具體的業務邏輯。

代理模式有多種型別,例如靜態代理、動態代理等,代理模式也有自己的優缺點,使用時需要根據具體的場景和需求來選擇合適的型別和方式。

靜態代理實現

下面我們用Java程式碼來實現一個靜態代理的例子:

// 抽象主題介面
public interface Subject {
    // 定義一個抽象方法
    void request();
}

// 真實主題類
public class RealSubject implements Subject {
    // 實現抽象方法
    @Override
    public void request() {
        // 真實的業務邏輯
        System.out.println("RealSubject is doing something...");
    }
}

// 代理主題類
public class Proxy implements Subject {
    // 持有一個真實主題的參照
    private RealSubject realSubject;

    // 構造方法,傳入一個真實主題物件
    public Proxy(RealSubject realSubject) {
        this.realSubject = realSubject;
    }

    // 實現抽象方法
    @Override
    public void request() {
        // 在呼叫真實主題之前,可以執行一些額外操作
        System.out.println("Proxy is doing something before...");
        // 呼叫真實主題的方法
        realSubject.request();
        // 在呼叫真實主題之後,可以執行一些額外操作
        System.out.println("Proxy is doing something after...");
    }
}

// 使用者端類
public class Client {
    public static void main(String[] args) {
        // 建立一個真實主題物件
        RealSubject realSubject = new RealSubject();
        // 建立一個代理物件,並傳入真實主題物件
        Proxy proxy = new Proxy(realSubject);
        // 使用代理物件來呼叫抽象方法
	    proxy.request();
	}
}
執行結果如下:
Proxy is doing something before...
RealSubject is doing something...
Proxy is doing something after...

從執行結果可以看出,代理物件在呼叫真實物件的方法之前和之後,都執行了一些額外的操作,從而對真實物件進行了增強或控制。

動態代理實現

動態代理是一種特殊的代理模式,它可以在執行時動態地建立代理物件,而不需要事先定義代理類。動態代理可以更靈活地適應不同的場景和需求,但是也更復雜和難以理解。

這個例子是使用JDK動態代理來實現一個紀錄檔代理,它可以在呼叫目標物件的方法之前和之後,記錄相關的紀錄檔資訊。程式碼如下:

// 抽象主題介面
public interface Subject {
    // 定義一個抽象方法
    void request();
}

// 真實主題類
public class RealSubject implements Subject {
    // 實現抽象方法
    @Override
    public void request() {
        // 真實的業務邏輯
        System.out.println("RealSubject is doing something...");
    }
}

// 紀錄檔處理器類,實現了InvocationHandler介面,用於定義代理邏輯
public class LogHandler implements InvocationHandler {
    // 持有一個目標物件的參照
    private Object target;

    // 構造方法,傳入一個目標物件
    public LogHandler(Object target) {
        this.target = target;
    }

    // 實現invoke方法,用於呼叫目標物件的方法,並在之前和之後執行紀錄檔操作
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 在呼叫目標物件之前,記錄開始時間
        long startTime = System.currentTimeMillis();
        System.out.println("開始執行" + method.getName() + "方法...");
        // 呼叫目標物件的方法,並獲取返回值
        Object result = method.invoke(target, args);
        // 在呼叫目標物件之後,記錄結束時間和耗時
        long endTime = System.currentTimeMillis();
        long duration = endTime - startTime;
        System.out.println("結束執行" + method.getName() + "方法,耗時" + duration + "毫秒");
        // 返回結果
        return result;
    }
}

// 使用者端類
public class Client {
    public static void main(String[] args) {
        // 建立一個真實主題物件
        RealSubject realSubject = new RealSubject();
        // 建立一個紀錄檔處理器物件,並傳入真實主題物件
        LogHandler logHandler = new LogHandler(realSubject);
        // 使用Proxy類的靜態方法newProxyInstance來動態地建立一個代理物件,傳入真實主題物件的類載入器、介面和處理器
        Subject proxy = (Subject) Proxy.newProxyInstance(realSubject.getClass().getClassLoader(), realSubject.getClass().getInterfaces(), logHandler);
        // 使用代理物件來呼叫抽象方法
        proxy.request();
    }
}

執行結果如下:

開始執行request方法...
RealSubject is doing something...
結束執行request方法,耗時1毫秒

從執行結果可以看出,代理物件在呼叫真實物件的方法之前和之後,都執行了一些紀錄檔操作,從而對真實物件進行了增強。

優缺點

靜態代理模式

優點:

  • 代理模式可以實現對真實物件的功能擴充套件或保護,而不需要修改原有的物件,符合開閉原則。
  • 代理模式可以實現對真實物件的存取控制或延遲載入,提高系統的效能和安全性。
  • 代理模式可以實現對真實物件的透明存取,使用者端只需要使用抽象主題的介面,而不需要關心真實物件和代理物件的細節。

缺點:

  • 代理模式會增加系統的複雜度和開銷,因為需要建立和維護代理物件。
  • 代理模式可能會降低系統的響應速度,因為每次呼叫真實物件的方法都需要經過代理物件。

動態代理模式

優點:

  • 動態代理可以在執行時動態地建立代理物件,而不需要事先定義代理類,這樣可以減少程式碼量和提高開發效率。
  • 動態代理可以根據不同的目標物件和需求,靈活地生成不同的代理物件,這樣可以增加系統的可延伸性和可維護性。
  • 動態代理可以實現對目標物件的透明存取,使用者端只需要使用抽象主題的介面,而不需要關心真實物件和代理物件的細節。

缺點:

  • 動態代理需要使用反射和位元組碼技術來生成代理物件,這樣會增加系統的複雜度和開銷,也可能會影響系統的效能和穩定性。
  • 動態代理需要遵循一些約束和限制,例如JDK動態代理只能代理實現了介面的類,CGLIB動態代理不能代理final類或方法等,這樣會降低系統的靈活性和通用性。
  • 動態代理比靜態代理更難以理解和掌握,需要有一定的基礎知識和經驗才能使用好動態代理。

運用場景

  • 當我們需要存取一個遠端物件時,可以使用遠端代理,它可以隱藏遠端物件的位置和通訊細節,讓使用者端像存取本地物件一樣存取遠端物件。
  • 當我們需要建立一個開銷很大的物件時,可以使用虛擬代理,它可以在真正需要的時候才建立真實物件,從而實現延遲載入和節省資源。
  • 當我們需要給一個物件增加一些額外的功能時,可以使用裝飾代理,它可以在不修改原有物件的情況下,給物件新增一些新的行為或屬性。
  • 當我們需要給一個物件增加一些存取控制或安全保護時,可以使用保護代理,它可以根據不同的使用者或角色,對物件的存取進行限制或檢查。
  • 當我們需要給一個物件增加一些紀錄檔記錄或效能監控時,可以使用紀錄檔代理或效能代理,它可以在呼叫物件的方法之前或之後,記錄相關的資訊或資料。

總結

代理模式是一種常用的結構型設計模式,它可以讓我們通過一個代理物件來間接存取一個真實物件,從而實現對目標物件的功能擴充套件或保護。代理模式有三個主要角色:抽象主題、真實主題和代理主題。代理模式有多種型別,例如靜態代理、動態代理、遠端代理、虛擬代理等。代理模式有自己的優缺點,使用時需要根據具體的場景和需求來選擇合適的型別和方式。