本篇介紹代理模式(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() |
+--------------+
以程式表示如下。Subject
為RealSubject
實作的介面,用以對外提供可呼叫的方法。
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)看起來非常類似,因為兩者都是透過新增實作原物件介面的類別來額外作些事,但兩者的差別為目的不同,代理模式主要用於原物件的存取控制,而裝飾者模式則專注在功能的擴充。
在某些時候其實兩者是一樣的,不需硬要去區分是屬於哪種設計模式,因為設計模式在應用上不是非黑即白,設計模式的種類主要是幫助我們了解要解決的問題。
沒有留言:
張貼留言