「每天一個知識點」精講設計模式之代理模式

點擊上方"java全棧技術"關注,每天學習一個java知識點

代理模式是常用的結構型設計模式之一,當無法直接訪問某個對象或訪問某個對象存在困難時可以通過一個代理對象來間接訪問,為了保證客戶端使用的透明性,所訪問的真實對象與代理對象需要實現相同的接口。根據代理模式的使用目的不同,代理模式又可以分為多種類型,例如保護代理、遠程代理、虛擬代理、緩衝代理等,它們應用於不同的場合,滿足用戶的不同需求。


1 代理模式概述

近年來,代購已逐步成為電子商務的一個重要分支。何謂代購,簡單來說就是找人幫忙購買所需要的商品,當然你可能需要向實施代購的人支付一定的費用。代購通常分為兩種類型:一種是因為在當地買不到某件商品,又或者是因為當地這件商品的價格比其他地區的貴,因此託人在其他地區甚至國外購買該商品,然後通過快遞發貨或者直接攜帶回來;還有一種代購,由於消費者對想要購買的商品相關信息的缺乏,自已無法確定其實際價值而又不想被商家宰,只好委託中介機構幫其講價或為其代買。代購網站為此應運而生,它為消費者提供在線的代購服務,如果看中某國外購物網站上的商品,可以登錄代購網站填寫代購單並付款,代購網站會幫助進行購買然後通過快遞公司將商品發送給消費者。商品代購過程如圖1所示:

「每天一個知識點」精講設計模式之代理模式

圖1 商品代購示意圖

在軟件開發中,也有一種設計模式可以提供與代購網站類似的功能。由於某些原因,客戶端不想或不能直接訪問一個對象,此時可以通過一個稱之為“代理”的第三者來實現間接訪問,該方案對應的設計模式被稱為代理模式。

代理模式是一種應用很廣泛的結構型設計模式,而且變化形式非常多,常見的代理形式包括遠程代理、保護代理、虛擬代理、緩衝代理、智能引用代理等,後面將學習這些不同的代理形式。

代理模式定義如下:

代理模式:給某一個對象提供一個代理或佔位符,並由代理對象來控制對原對象的訪問。

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

代理模式是一種對象結構型模式。在代理模式中引入了一個新的代理對象,代理對象在客戶端對象和目標對象之間起到中介的作用,它去掉客戶不能看到的內容和服務或者增添客戶需要的額外的新服務。

2 代理模式結構與實現

2.1 模式結構

代理模式的結構比較簡單,其核心是代理類,為了讓客戶端能夠一致性地對待真實對象和代理對象,在代理模式中引入了抽象層,代理模式結構如圖2所示:

「每天一個知識點」精講設計模式之代理模式

圖2 代理模式結構圖

由圖2可知,代理模式包含如下三個角色:

(1) Subject(抽象主題角色):它聲明瞭真實主題和代理主題的共同接口,這樣一來在任何使用真實主題的地方都可以使用代理主題,客戶端通常需要針對抽象主題角色進行編程。

(2) Proxy(代理主題角色):它包含了對真實主題的引用,從而可以在任何時候操作真實主題對象;在代理主題角色中提供一個與真實主題角色相同的接口,以便在任何時候都可以替代真實主題;代理主題角色還可以控制對真實主題的使用,負責在需要的時候創建和刪除真實主題對象,並對真實主題對象的使用加以約束。通常,在代理主題角色中,客戶端在調用所引用的真實主題操作之前或之後還需要執行其他操作,而不僅僅是單純調用真實主題對象中的操作。

(3) RealSubject(真實主題角色):它定義了代理角色所代表的真實對象,在真實主題角色中實現了真實的業務操作,客戶端可以通過代理主題角色間接調用真實主題角色中定義的操作。

2.2 模式實現

代理模式的結構圖比較簡單,但是在真實的使用和實現過程中要複雜很多,特別是代理類的設計和實現。

抽象主題類聲明瞭真實主題類和代理類的公共方法,它可以是接口、抽象類或具體類,客戶端針對抽象主題類編程,一致性地對待真實主題和代理主題,典型的抽象主題類代碼如下:

「每天一個知識點」精講設計模式之代理模式

真實主題類繼承了抽象主題類,提供了業務方法的具體實現,其典型代碼如下:

「每天一個知識點」精講設計模式之代理模式

代理類也是抽象主題類的子類,它維持一個對真實主題對象的引用,調用在真實主題中實現的業務方法,在調用時可以在原有業務方法的基礎上附加一些新的方法來對功能進行擴充或約束,最簡單的代理類實現代碼如下:

「每天一個知識點」精講設計模式之代理模式

在實際開發過程中,代理類的實現比上述代碼要複雜很多,代理模式根據其目的和實現方式不同可分為很多種類,其中常用的幾種代理模式簡要說明如下:

(1) 遠程代理(Remote Proxy)為一個位於不同的地址空間的對象提供一個本地的代理對象,這個不同的地址空間可以是在同一臺主機中,也可是在另一臺主機中,遠程代理又稱為大使(Ambassador)。

(2) 虛擬代理(Virtual Proxy):如果需要創建一個資源消耗較大的對象,先創建一個消耗相對較小的對象來表示,真實對象只在需要時才會被真正創建。

(3) 保護代理(Protect Proxy)控制對一個對象的訪問,可以給不同的用戶提供不同級別的使用權限。

(4) 緩衝代理(Cache Proxy)為某一個目標操作的結果提供臨時的存儲空間,以便多個客戶端可以共享這些結果。

(5) 智能引用代理(Smart Reference Proxy)當一個對象被引用時,提供一些額外的操作,例如將對象被調用的次數記錄下來等。

在這些常用的代理模式中,有些代理類的設計非常複雜,例如遠程代理類,它封裝了底層網絡通信和對遠程對象的調用,其實現較為複雜。

3 代理模式應用實例

下面通過一個應用實例來進一步學習和理解代理模式。

1. 實例說明

某軟件公司承接了某信息諮詢公司的收費商務信息查詢系統的開發任務,該系統的基本需求如下:

(1) 在進行商務信息查詢之前用戶需要通過身份驗證,只有合法用戶才能夠使用該查詢系統;

(2) 在進行商務信息查詢時系統需要記錄查詢日誌,以便根據查詢次數收取查詢費用。

該軟件公司開發人員已完成了商務信息查詢模塊的開發任務,現希望能夠以一種松耦合的方式向原有系統增加身份驗證和日誌記錄功能,客戶端代碼可以無區別地對待原始的商務信息查詢模塊和增加新功能之後的商務信息查詢模塊,而且可能在將來還要在該信息查詢模塊中增加一些新的功能。

試使用代理模式設計並實現該收費商務信息查詢系統。

2. 實例分析及類圖

通過分析,可以採用一種間接訪問的方式來實現該商務信息查詢系統的設計,在客戶端對象和信息查詢對象之間增加一個代理對象,讓代理對象來實現身份驗證和日誌記錄等功能,而無須直接對原有的商務信息查詢對象進行修改,如圖3所示:

「每天一個知識點」精講設計模式之代理模式

圖3 商務信息查詢系統設計方案示意圖

在圖3中,客戶端對象通過代理對象間接訪問具有商務信息查詢功能的真實對象,在代理對象中除了調用真實對象的商務信息查詢功能外,還增加了身份驗證和日誌記錄等功能。使用代理模式設計該商務信息查詢系統,結構圖如圖4所示。

「每天一個知識點」精講設計模式之代理模式

圖4 商務信息查詢系統結構圖

在圖4中,業務類AccessValidator用於驗證用戶身份,業務類Logger用於記錄用戶查詢日誌,Searcher充當抽象主題角色,RealSearcher充當真實主題角色,ProxySearcher充當代理主題角色。

3. 實例代碼

(1) AccessValidator:身份驗證類,業務類,它提供方法Validate()來實現身份驗證。

「每天一個知識點」精講設計模式之代理模式

(2) Logger:日誌記錄類,業務類,它提供方法Log()來保存日誌。

「每天一個知識點」精講設計模式之代理模式

(3) Searcher:抽象查詢類,充當抽象主題角色,它聲明瞭DoSearch()方法。

「每天一個知識點」精講設計模式之代理模式

(4) RealSearcher:具體查詢類,充當真實主題角色,它實現查詢功能,提供方法DoSearch()來查詢信息。

「每天一個知識點」精講設計模式之代理模式

(5) ProxySearcher:代理查詢類,充當代理主題角色,它是查詢代理,維持了對RealSearcher對象、AccessValidator對象和Logger對象的引用。

「每天一個知識點」精講設計模式之代理模式

「每天一個知識點」精講設計模式之代理模式

(6) 配置文件App.config,在配置文件中存儲了代理主題類類名。

「每天一個知識點」精講設計模式之代理模式

(7) Program:客戶端測試類

「每天一個知識點」精講設計模式之代理模式

4. 結果及分析

編譯並運行程序,輸出結果如下:

在數據庫中驗證用戶'楊過'是否是合法用戶?

'楊過'登錄成功!

用戶'楊過'使用關鍵詞'玉女心經'查詢商務信息!

更新數據庫,用戶'楊過'查詢次數加1!

本實例是保護代理智能引用代理的應用實例,在代理類ProxySearcher中實現對真實主題類的權限控制和引用計數,如果需要在訪問真實主題時增加新的訪問控制機制和新功能,只需增加一個新的代理類,再修改配置文件,在客戶端代碼中使用新增代理類即可,源代碼無須修改,符合開閉原則。

4 遠程代理

遠程代理(Remote Proxy)是一種常用的代理模式,它使得客戶端程序可以訪問在遠程主機上的對象,遠程主機可能具有更好的計算性能與處理速度,可以快速響應並處理客戶端的請求。遠程代理可以將網絡的細節隱藏起來,使得客戶端不必考慮網絡的存在。客戶端完全可以認為被代理的遠程業務對象是在本地而不是在遠程,而遠程代理對象承擔了大部分的網絡通信工作,並負責對遠程業務方法的調用。

遠程代理示意圖如圖15-5所示,客戶端對象不能直接訪問遠程主機中的業務對象,只能採取間接訪問的方式。遠程業務對象在本地主機中有一個代理對象,該代理對象負責對遠程業務對象的訪問和網絡通信,它對於客戶端對象而言是透明的。客戶端無須關心實現具體業務的是誰,只需要按照服務接口所定義的方式直接與本地主機中的代理對象交互即可。

「每天一個知識點」精講設計模式之代理模式

圖15 遠程代理示意圖

在基於.NET平臺的分佈式技術,例如DCOM(Distribute Component Object Model,分佈式組件對象模型)、Web Service中,都應用了遠程代理模式,大家可以查閱相關資料進行擴展學習。

5 虛擬代理

虛擬代理(Virtual Proxy)也是一種常用的代理模式,對於一些佔用系統資源較多或者加載時間較長的對象,可以給這些對象提供一個虛擬代理。在真實對象創建成功之前虛擬代理扮演真實對象的替身,而當真實對象創建之後,虛擬代理將用戶的請求轉發給真實對象。

通常,在以下兩種情況下可以考慮使用虛擬代理:

(1) 由於對象本身的複雜性或者網絡等原因導致一個對象需要較長的加載時間,此時可以用一個加載時間相對較短的代理對象來代表真實對象。通常在實現時可以結合多線程技術,一個線程用於顯示代理對象,其他線程用於加載真實對象。這種虛擬代理模式可以應用在程序啟動的時候,由於創建代理對象在時間和處理複雜度上要少於創建真實對象,因此,在程序啟動時,可以用代理對象代替真實對象初始化,大大加速了系統的啟動時間。當需要使用真實對象時,再通過代理對象來引用,而此時真實對象可能已經成功加載完畢,可以縮短用戶的等待時間。

(2) 當一個對象的加載十分耗費系統資源的時候,也非常適合使用虛擬代理。虛擬代理可以讓那些佔用大量內存或處理起來非常複雜的對象推遲到使用它們的時候才創建,而在此之前用一個相對來說佔用資源較少的代理對象來代表真實對象,再通過代理對象來引用真實對象。為了節省內存,在第一次引用真實對象時再創建對象,並且該對象可被多次重用,在以後每次訪問時需要檢測所需對象是否已經被創建,因此在訪問該對象時需要進行存在性檢測,這需要消耗一定的系統時間,但是可以節省內存空間,這是一種用時間換取空間的做法。

無論是以上哪種情況,虛擬代理都是用一個“虛假”的代理對象來代表真實對象,通過代理對象來間接引用真實對象,可以在一定程度上提高系統的性能。

6 緩衝代理

緩衝代理(Cache Proxy)也是一種較為常用的代理模式,它為某一個操作的結果提供臨時的緩存存儲空間,以便在後續使用中能夠共享這些結果,從而可以避免某些方法的重複執行,優化系統性能。

在微軟示例項目PetShop 4.0的業務邏輯層(Business Logic Layer, BLL)中定義了Product、Category、Item等類,它們封裝了相關的業務方法,用於調用數據訪問層(Data Access Layer, DAL)對象訪問數據庫,以獲取相關數據。為了改進系統性能,PetShop 4.0為這些實現方法增加緩存機制,引入一個新的對象去控制原來的BLL業務邏輯對象,這些新的對象對應於代理模式中的代理對象。在引入代理模式後,實現了在緩存級別上對業務對象的封裝,增強了對業務對象的控制,如果需要訪問的數據在緩存中已經存在,則無須再重複執行獲取數據的方法,直接返回存儲在緩存中的數據即可。由於原有業務對象(真實對象)和新增代理對象暴露在外的方法是一致的,因而對於調用方即客戶端而言,調用代理對象與真實對象並沒有實質的區別。

這些新引入的代理類包括ProductDataProxy、CategoryDataProxy和ItemDataProxy等。下面以PetShop.BLL.Product業務對象為例進行說明,PetShop 4.0為其建立了代理對象ProductDataProxy,並在ProductDataProxy的GetProductsByCategory()方法中調用了業務邏輯層Product類的GetProductsByCategory()方法,同時增加了緩存機制。如圖15-6所示:

「每天一個知識點」精講設計模式之代理模式

圖6 PetShop4.0緩存代理示意圖

在ProductDataProxy類中存在如下代碼片段:

「每天一個知識點」精講設計模式之代理模式

「每天一個知識點」精講設計模式之代理模式

在上述代碼中,AggregateCacheDependency是從.NET Framework 2.0開始新增的一個類,它負責監視依賴項對象的集合。當這個集合中的任意一個依賴項對象發生改變時,該依賴項對象對應的緩存對象都將被自動移除。在此不對AggregateCacheDependency進行詳細說明,大家可以查閱相關資料進行擴展學習。

與業務邏輯層Product對象的GetProductsByCategory()方法相比,上述代碼增加了緩存機制。當緩存內不存在相關數據項時,則直接調用業務邏輯層Product的GetProductsByCategory()方法來獲取數據,並將其與對應的AggregateCacheDependency對象一起存儲在緩存中。在ProductDataProxy類的每一個業務方法中都實例化了Product類,再調用Product類的相應方法,因此ProductDataProxy與Product之間屬於依賴關係,這是標準代理模式的一種變形,可以按照標準代理模式對其進行改進,包括引入高層的抽象接口。

7 代理模式效果與適用場景

代理模式是常用的結構型設計模式之一,它為對象的間接訪問提供了一個解決方案,可以對對象的訪問進行控制。代理模式類型較多,其中遠程代理、虛擬代理、保護代理等在軟件開發中應用非常廣泛。

7.1 模式優點

代理模式的共同優點如下:

(1) 能夠協調調用者和被調用者,在一定程度上降低了系統的耦合度。

(2) 客戶端可以針對抽象主題角色進行編程,增加和更換代理類無須修改源代碼,符合開閉原則,系統具有較好的靈活性和可擴展性。

此外,不同類型的代理模式也具有獨特的優點,例如:

(1) 遠程代理為位於兩個不同地址空間對象的訪問提供了一種實現機制,可以將一些消耗資源較多的對象和操作移至性能更好的計算機上,提高系統的整體運行效率。

(2) 虛擬代理通過一個消耗資源較少的對象來代表一個消耗資源較多的對象,可以在一定程度上節省系統的運行開銷。

(3) 緩衝代理為某一個操作的結果提供臨時的緩存存儲空間,以便在後續使用中能夠共享這些結果,優化系統性能,縮短執行時間。

(4) 保護代理可以控制對一個對象的訪問權限,為不同用戶提供不同級別的使用權限。

7.2 模式缺點

代理模式的主要缺點如下:

(1) 由於在客戶端和真實主題之間增加了代理對象,因此有些類型的代理模式可能會造成請求的處理速度變慢,例如保護代理。

(2) 實現代理模式需要額外的工作,而且有些代理模式的實現過程較為複雜,例如遠程代理。

7.3 模式適用場景

代理模式的類型較多,不同類型的代理模式有不同的優缺點,它們應用於不同的場合:

(1) 當客戶端對象需要訪問遠程主機中的對象時可以使用遠程代理。

(2) 當需要用一個消耗資源較少的對象來代表一個消耗資源較多的對象,從而降低系統開銷、縮短運行時間時可以使用虛擬代理,例如一個對象需要很長時間才能完成加載時。

(3) 當需要為某一個被頻繁訪問的操作結果提供一個臨時存儲空間,以供多個客戶端共享訪問這些結果時可以使用緩衝代理。通過使用緩衝代理,系統無須在客戶端每一次訪問時都重新執行操作,只需直接從臨時緩衝區獲取操作結果即可。

(4) 當需要控制對一個對象的訪問,為不同用戶提供不同級別的訪問權限時可以使用保護代理。

(5) 當需要為一個對象的訪問(引用)提供一些額外的操作時可以使用智能引用代理。

動態代理的實現

動態代理作為代理模式的一種擴展形式,廣泛應用於框架(尤其是基於AOP的框架)的設計與開發,本文將通過實例來講解Java動態代理的實現過程。

通常情況下,代理模式中的每一個代理類在編譯之後都會生成一個class文件,代理類所實現的接口和所代理的方法都被固定,這種代理被稱之為靜態代理(Static Proxy)

。那麼有沒有一種機制能夠讓系統在運行時動態創建代理類?答案就是本文將要介紹的動態代理(Dynamic Proxy)。動態代理是一種較為高級的代理模式,它在事務管理、AOP(Aspect-OrientedProgramming,面向方面編程)等領域都發揮了重要的作用。

在傳統的代理模式中,客戶端通過Proxy類調用RealSubject類的request()方法,同時還可以在代理類中封裝其他方法(如preRequest()和postRequest()等)。如果按照這種方法使用代理模式,那麼代理類和真實主題類都應該是事先已經存在的,代理類的接口和所代理方法都已明確指定,如果需要為不同的真實主題類提供代理類或者代理一個真實主題類中的不同方法,都需要增加新的代理類,這將導致系統中的類個數急劇增加,因此需要想辦法減少系統中類的個數。動態代理可以讓系統能夠根據實際需要來動態創建代理類,讓同一個代理類能夠代理多個不同的真實主題類而且可以代理不同的方法。

從JDK 1.3開始,Java語言提供了對動態代理的支持,Java語言實現動態代理時需要用到位於java.lang.reflect包中的一些類,現簡要說明如下:

(1) Proxy類

Proxy類提供了用於創建動態代理類和實例對象的方法,它是所創建的動態代理類的父類,它最常用的方法如下:

  • public static Class> getProxyClass(ClassLoader loader,Class>... interfaces):該方法用於返回一個Class類型的代理類,在參數中需要提供類加載器並需要指定代理的接口數組(與真實主題類的接口列表一致)。
  • public static Object newProxyInstance(ClassLoader loader, Class>[]interfaces, InvocationHandler h):該方法用於返回一個動態創建的代理類的實例,方法中第一個參數loader表示代理類的類加載器,第二個參數interfaces表示代理類所實現的接口列表(與真實主題類的接口列表一致),第三個參數h表示所指派的調用處理程序類。

(2) InvocationHandler接口

InvocationHandler接口是代理處理程序類的實現接口,該接口作為代理實例的調用處理者的公共父類,每一個代理類的實例都可以提供一個相關的具體調用處理者(InvocationHandler接口的子類)。在該接口中聲明瞭如下方法:

  • public Object invoke(Objectproxy, Method method, Object[] args):該方法用於處理對代理類實例的方法調用並返回相應的結果,當一個代理實例中的業務方法被調用時將自動調用該方法。invoke()方法包含三個參數,其中第一個參數proxy表示代理類的實例,第二個參數method表示需要代理的方法,第三個參數args表示代理方法的參數數組。

動態代理類需要在運行時指定所代理真實主題類的接口,客戶端在調用動態代理對象的方法時,調用請求會將請求自動轉發給InvocationHandler對象的invoke()方法,由invoke()方法來實現對請求的統一處理。

下面通過一個簡單實例來學習如何使用動態代理模式:

Sunny軟件公司欲為公司OA系統數據訪問層DAO增加方法調用日誌,記錄每一個方法被調用的時間和調用結果,現使用動態代理進行設計和實現。

本實例完整代碼如下所示:

「每天一個知識點」精講設計模式之代理模式

「每天一個知識點」精講設計模式之代理模式

「每天一個知識點」精講設計模式之代理模式

「每天一個知識點」精講設計模式之代理模式

「每天一個知識點」精講設計模式之代理模式

編寫如下客戶端測試代碼:

「每天一個知識點」精講設計模式之代理模式

編譯並運行程序,輸出結果如下:

調用時間:13:47:14

查詢ID為張無忌的用戶信息成功!

方法調用結束!

------------------------------

調用時間:13:47:14

刪除ID為D002的文檔信息失敗!

方法調用結束!

通過使用動態代理,我們可以實現對多個真實主題類的統一代理和集中控制。

注:JDK中提供的動態代理只能代理一個或多個接口,如果需要動態代理具體類或抽象類,可以使用CGLib(Code Generation Library)等工具,CGLib是一個功能較為強大、性能和質量也較好的代碼生成包,在許多AOP框架中都得以廣泛應用,大家可以自行查閱相關資料來學習CGLib。


分享到:


相關文章: