網頁

2020/11/17

Java 設計模式 代理模式 Proxy Pattern

本篇介紹代理模式(Proxy Pattern)。

代理模式屬於GoF中的Structural Pattern

Provide a surrogate or placeholder for another object to control access to it.


「代理(Proxy)」這個名詞不僅用在軟體工程,也用在其他領域,例如「代理伺服器(Proxy Server)」用來代理真正伺服器的伺服器;「代理商」用來代理原製造商的商品買賣和販售;「代理人」用來代理本人處理事務。所以代理顧名思義就是代理原本的物件去處理事情,但注意真正做事的依舊是原本的物件,代理的角色只是客戶端接觸原物件的第一道關卡,負責卡控客戶端對原物件的存取。

代理模式的使用時機為,當不想讓客戶端(呼叫端/調用端)直接使用原本的物件,則可以建立一個原物件的代理物件(Proxy )讓客戶端來使用。


代理模式常見的應用包括:

  • 虛擬代理(virtual proxy):延遲物件的初始化(lazy initialization)。當原物件的初始化很耗資源時,可透過代理延遲原物件的初始化,只有在真正被調用時才進行加載。
  • 保護代理(protection proxy):依調用對象或傳入參數控制存取原物件的權限。
  • 遠端代理(remote proxy):封裝複雜的網路呼叫程序。
  • 日誌代理(logging proxy):增加原物件被調用的日誌記錄。
  • 快取代理(cache proxy):增加調用原物件的快取。
  • 智慧代理(smart proxy):偵測、鎖定、釋放原物件的使用。

代理模式有以下角色:

  • Real Subject(真實物件):原本被客戶端調用的對象,真正功能的提供者。為Proxy的代理對象。
  • Subject:Real Subject實作的介面,為Real Subject對外公開的功能。
  • Proxy(代理物件):Real Subject的代理物件,替代原物件來作為被客戶端調用的對象。

下面是未使用代理模式的UML類別圖。可以看到客戶端(Client)透過介面(Subject)直接調用原物件(Real Subject)使用。

                            +---------------+
+------------+              | <<Interface>> |
|            |              |               |
|   Client   |-----use----->|    Subject    |
|            |              |               |
+------------+              +---------------+
                            |    service()  |
                            +---------------+
                                    ^
                                    |
                               implements
                                    |
                            +--------------+
                            |              |
                            | Real Subject |
                            |              |
                            +--------------+
                            |  +service()  |
                            +--------------+

以程式表示如下。SubjectRealSubject實作的介面,用以對外提供可呼叫的方法。

Subject

public interface Subject {

    void service();

}

RealSubject為提供功能的原物件,這邊用Thread.sleep()模擬執行時耗費的時間。

RealSubject

public class RealSubject implements Subject {

    @Override
    public void service() {
        try {
            Thread.sleep(2000L); // 2秒
            System.out.println("Do something...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

Client為客戶端,透過介面Subject調用原物件RealSubject的功能。

Client

public class Client {

    public static void main(String[] args) {
        Subject subject = new RealSubject();
        subject.service();
    }

}


下面則是使用代理模式的UML類別圖。客戶端(Client)改為透過介面(Subject)調用代理物件(Proxy),然後由代理物件去調用原物件(Real Subject)。

                            +---------------+
+------------+              | <<Interface>> |
|            |              |               |
|   Client   |-----use----->|    Subject    |
|            |              |               |
+------------+              +---------------+
                            |    service()  |
                            +---------------+
                                    ^
                                    |
                               implements
                                    |
                     +--------------+--------------+
                     |                             |
              +-------------+               +--------------+
              |             |               |              |
              |    Proxy    |   aggregate   | Real Subject |
              |             |-------------->|              |
              +-------------+               +--------------+
              |  +service() |               |  +service()  |
              +-------------+               +--------------+

新增一個代理類別Proxy實作原物件的介面Subject,並以原物件為成員變數作為實際方法的提供者。如此代理物件就能在客戶端調用時去幫原物件額外處理一些事,例如日誌記錄。

Proxy

public class Proxy implements Subject {

    private final Subject subject;

    public Proxy(Subject subject) {
        this.subject = subject;
    }

    @Override
    public void service() {
        System.out.println("service() 開始於:" + new Date());

        subject.service();

        System.out.println("service() 結束於:" + new Date());
    }
}

客戶端改調用代理物件Proxy

Client

public class Client {

    public static void main(String[] args) {
        Subject subject = new Proxy(new RealSubject());
        subject.service();
    }

}

執行結果如下。

service() 開始於:Wed Nov 18 10:23:16 CST 2020
Do something...
service() 結束於:Wed Nov 18 10:23:18 CST 2020

當然原物件的建立也可以直接在代理內生成,不由外部提供。

Proxy

public class Proxy implements Subject {

    private final Subject subject = new RealSubject();

    public Proxy() {
    }

    @Override
    public void service() {
        System.out.println("service() 開始於:" + new Date());

        subject.service();

        System.out.println("service() 結束於:" + new Date());
    }
}

以上介紹的代理模式屬於靜態代理,也就是必須事先定義好代理類別;另一種代理為動態代理(Dynamic Proxy)(又分Java Proxy動態代理與CGLIB動態代理)能在執行期間(runtime)動態生成代理物件,不用事先定義好原物件的代理類別,在Java中其原理是基於反射來生成。然而不論是靜態代理或動態代理兩者核心觀念相同,都是希望透過代理來控制原物件的存取。

代理模式在Java最典型的應用就是Spring AOP技術,透過原物件的代理來攔截方法插入額外的邏輯。

而代理模式(Proxy Pattern)與裝飾者模式(Decorator Pattern)看起來非常類似,因為兩者都是透過新增實作原物件介面的類別來額外作些事,但兩者的差別為目的不同,代理模式主要用於原物件的存取控制,而裝飾者模式則專注在功能的擴充。

在某些時候其實兩者是一樣的,不需硬要去區分是屬於哪種設計模式,因為設計模式在應用上不是非黑即白,設計模式的種類主要是幫助我們了解要解決的問題。


沒有留言:

張貼留言