1. 程式人生 > >軟體設計模式學習(十六)代理模式

軟體設計模式學習(十六)代理模式


> 當直接訪問某些物件存在問題時,可以通過一個代理物件來間接訪問,為了保證客戶端使用的透明性,所訪問的真實物件與代理物件需要實現相同的介面。
## 模式動機 某些情況下,一個客戶不想或不能直接引用一個物件,此時可以通過一個稱之為代理的第三者實現間接引用。代理物件在客戶端和目標物件之間起到中介作用,並且可以通過代理物件去掉客戶不能看到的內容和新增客戶需要的額外服務。
## 模式定義 給某一個物件提供一個代理,並由代理物件控制對原物件的引用。代理模式的英文叫做 Proxy 或 Surrogate,它是一種物件結構模式。
## 模式結構 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200514223526581.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0NTRE5faGFuZHNvbWU=,size_16,color_FFFFFF,t_70) 1. Subject(抽象主題角色) 聲明瞭真實主題和代理主題的公共介面,這樣一來在任何使用真實主題的地方都可以使用代理主題。客戶端針對抽象主題角色程式設計。 2. Proxy(代理主題角色) 代理主題角色內部包含對真實主題的引用,從而可以在任何時候操作真實主題角色。 代理主題角色中提供一個與真實主題角色相同的介面,以便在任何時候替代真實主體。 代理主題角色還可以控制對真實主題的使用,負責在需要時建立和刪除真實主題物件,並對真實主題物件的使用加以約束。 代理角色通常在客戶端呼叫所引用的真實主題操作之前或之後執行其他操作,而不僅僅只是單純呼叫真實主題物件中的操作。 3. RealSubject(真實主題角色) 真實主題角色定義了代理角色所代表的真實物件,真實主題角色中實現真實的業務,客戶端通過代理主題角色間接呼叫真實主題角色中定義的方法。
## 模式例項與解析 在一個論壇已註冊使用者和遊客許可權不同,已註冊使用者擁有發帖、修改註冊資訊、修改自己帖子等功能;而遊客只能看到別人發的貼子,沒有其他許可權。本例項中我們使用代理模式中的保護代理,該代理用於控制對一個物件的訪問,可以給不同使用者提供不同級別的使用許可權。 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/2020051422355133.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0NTRE5faGFuZHNvbWU=,size_16,color_FFFFFF,t_70) 1. 抽象主題角色 AbstractPermission(抽象許可權類) AbstractPermission 作為抽象許可權類,充當抽象主題角色,在其中聲明瞭真實主題角色所提供的業務方法,它是真實主題角色和代理主題角色的公共介面 ```java public interface AbstractPermission { public void modifyUserInfo(); public void viewNote(); public void publishNote(); public void modifyNote(); public void setLevel(int level); } ``` 2. 真實主題角色 RealPermission(真實許可權類) RealPermission 是真實主題角色,它實現了在抽象主題角色中定義的方法,由於種種原因客戶端無法訪問其中內容。 ```java public class RealPermission implements AbstractPermission { @Override public void modifyUserInfo() { System.out.println("修改使用者資訊"); } @Override public void viewNote() { } @Override public void publishNote() { System.out.println("釋出新帖"); } @Override public void modifyNote() { System.out.println("修改發帖內容"); } @Override public void setLevel(int level) { } } ``` 3. 代理主題角色 PermissionProxy(許可權代理類) PermissionProxy 是代理主題角色,它也實現了抽象主題角色介面,同時在 PermissionProxy 中定義了一個 RealPermission 物件,用於呼叫 RealPermission 中定義的真實業務方法。通過引入 PermissionProxy 類來對系統的使用許可權進行控制,這就是保護代理的用途。 ```java public class PermissionProxy implements AbstractPermission { private RealPermission permission = new RealPermission(); private int level = 0; @Override public void modifyUserInfo() { if (0 == level) { System.out.println("對不起,你沒有該許可權"); } else if (1 == level) { permission.modifyUserInfo(); } } @Override public void viewNote() { System.out.println("檢視帖子"); } @Override public void publishNote() { if (0 == level) { System.out.println("對不起,你沒有該許可權"); } else if (1 == level) { permission.publishNote(); } } @Override public void modifyNote() { if (0 == level) { System.out.println("對不起,你沒有該許可權"); } else if (1 == level) { permission.modifyNote(); } } @Override public void setLevel(int level) { this.level = level; } } ``` 4. 客戶端測試類 Client ```java public class Client { public static void main(String[] args) { AbstractPermission permission = new PermissionProxy(); permission.modifyUserInfo(); permission.viewNote(); permission.publishNote(); permission.modifyNote(); System.out.println("-------------------------"); permission.setLevel(1); permission.modifyUserInfo(); permission.viewNote(); permission.publishNote(); permission.modifyNote(); } } ``` 5. 執行結果 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200514223749530.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0NTRE5faGFuZHNvbWU=,size_16,color_FFFFFF,t_70) 代理類可以對使用者訪問許可權進行控制,因此有些使用者無權呼叫真實業務類的某些方法,當用戶許可權改變時,則可以訪問這些方法。如果需要增加並使用新的代理類,首先將新增代理類作為抽象主題角色的子類,實現在抽象主題中宣告的方法。
## 模式優缺點 代理模式優點如下: 1. 代理模式能協調呼叫者和被呼叫者,在一定程度上降低了系統的耦合度 2. 遠端代理使客戶端能訪問在遠端機器上的物件 3. 虛擬代理通過使用一個小物件來代表一個大物件,可以減少系統資源的消耗,對系統進行優化並提高速度 4. 保護代理可以控制對真實物件的使用許可權 代理模式缺點如下: 1. 由於在客戶端和真實主題之間增加了代理物件,因此可能會造成請求的處理速度變慢。 2. 有些代理模式的實現十分複雜。
## 模式使用環境 根據代理模式的使用目的,常見的代理模式有以下幾個型別。 1. 遠端(Remote)代理:為一個位於不同的地址空間的物件提供一個本地的代理物件,這個不同的地址空間可以在同一臺主機,也可以是另一臺主機。 2. 虛擬(Virtual)代理:如果需要建立一個資源消耗較大的物件,可以先建立一個消耗較小的物件來表示,真實物件只在需要時才會被真正建立。 3. Copy-on-Write代理:它是虛擬代理的一種,把複雜操作延遲到只在客戶端真正需要時才執行。 4. 保護(Protect or Access)代理:控制對一個物件的訪問,可以給不同的使用者提供不同級別的使用許可權。 5. 緩衝(Cache)代理:為某一個目標操作的結果提供臨時的儲存空間,以便多個客戶可以共享這些結果。 6. 防火牆(FireWall)代理:保護目標不讓惡意使用者接近 7. 同步化(Synchronization)代理:使幾個使用者能同時使用一個物件而沒有衝突 8. 智慧(Smart Reference)引用:當一個物件被引用時,提供一些額外的操作,如將此物件被呼叫的次數記錄下來
## 靜態代理 所謂靜態代理,就是由程式設計師建立或特定工具自動生成原始碼,也就是在編譯時就已經將介面,被代理類,代理類等確定下來。在程式執行之前,代理類的 .class 檔案就已經生成。簡單來說,上述的例項就屬於靜態代理,PermissionProxy 代理類是我們定義好的,在程式執行之前就已經編譯完成。
## 動態代理 傳統的代理模式中,客戶端通過 ProxySubject 呼叫 RealSubject 類的方法,同時還在代理類中封裝了其他方法,可以處理一些其他問題。如果按照這種方法使用代理模式,那麼真實主題角色必須是是事先已經存在的,並將其作為代理物件的內部成員屬性。如果一個真實主題角色必須對應一個代理主題角色,這將導致系統中的類的個數急劇增加,因此需要想辦法減少系統中類的個數。 Java 自帶的基於介面的動態代理(即只能實現介面的代理)能在執行時根據我們在 Java 程式碼中的指示動態生成的代理類,其實現相關類位於 java.lang.reflect 包,執行時動態地對某些東西作代理,主要涉及兩個類 1. InvocationHandler 介面 > InvocationHandler is the interface implemented by the invocation handler of a proxy instance. > Each proxy instance has an associated invocation handler.When a method is invoked on a proxy instance, the method invocation is encoded and dispatched to the invoke method of its invocation handler. InvocationHandler 是由代理例項的呼叫處理程式實現的介面。每個代理例項都有一個關聯的呼叫處理程式。InvocationHandler 介面中定義了 invoke 方法,當代理例項呼叫某個方法時,該方法的呼叫將被編碼並排程到其呼叫處理程式的 invoke 方法處理。 ```java /** * 處理代理例項上的方法呼叫並返回結果 * proxy 表示動態代理類 * method 表示需要代理的方法 * args 表示代理方法的引數陣列 */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; ``` 2. Proxy 類 ```java /** * 該類提供了用於為介面建立代理例項的靜態方法 */ public class Proxy implements java.io.Serializable { ... /** * 根據傳入的介面型別返回一個動態建立的代理類例項 * loader 表示被代理類的類載入器 * interfaces 表示被代理類實現的介面列表(與真實主題類的介面列表一致) * h 表示所指派的呼叫處理程式類 */ public static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) { ... } ``` 下面通過一個簡單例項來學習動態代理,現在有兩個真實主題類分別是 RealSubjectA 和 RealSubjectB,它們對於抽象主題類中定義的抽象方法 request() 提供了不同的實現,在不增加新的代理類的情況下,使得客戶端通過一個代理類來動態選擇所代理的真實主題物件 1. 抽象主題介面 AbstractSubject ```java public interface AbstractSubject { public void request(); } ``` 2. 真實主題類一 RealSubjectA ```java public class RealSubjectA implements AbstractSubject { @Override public void request() { System.out.println("真實主題類A"); } } ``` 3. 真實主題類二 RealSubjectB ```java public class RealSubjectB implements AbstractSubject { @Override public void request() { System.out.println("真實主題類B"); } } ``` 4. 動態代理類 DynamicProxy ```java public class DynamicProxy implements InvocationHandler { private Object obj; public DynamicProxy() {} public DynamicProxy(Object obj) { this.obj = obj; } // 實現 invoke() 方法,呼叫在真實主題類中定義的方法 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("呼叫之前"); // 利用反射呼叫方法,如果方法沒有返回值則為 null Object result = method.invoke(obj, args); System.out.println("呼叫之後"); return result; } } ``` 5. 客戶端測試類 Client ```java public class Client { public static void main(String[] args) { AbstractSubject subject = new RealSubjectA(); InvocationHandler handler = new DynamicProxy(subject); AbstractSubject subjectProxy = (AbstractSubject) Proxy.newProxyInstance(subject.getClass().getClassLoader(), subject.getClass().getInterfaces(), handler); subjectProxy.request(); System.out.println("-----------------------------"); subject = new RealSubjectB(); handler = new DynamicProxy(subject); subjectProxy = (AbstractSubject) Proxy.newProxyInstance(subject.getClass().getClassLoader(), subject.getClass().getInterfaces(), handler); subjectProxy.request(); } } ``` 6. 執行結果 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200514223822589.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0NTRE5faGFuZHNvbWU=,size_16,color_FFFFFF,t_70)