設計模式-代理模式(Proxy)-Java

設計模式-代理模式(Proxy)-Java


目錄




內容

1、前言

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

2、代理模式概述

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

  在軟件開發中,也有一種設計模式可以提供與代購網站類似的功能。由於某些原因,客戶端不想或不能直接訪問一個對象,此時可以通過一個稱之爲“代理”的第三者來實現間接訪問,該方案對應的設計模式被稱爲代理模式。代理模式是一種應用很廣泛的結構型設計模式,而且變化形式非常多,常見的代理形式包括遠程代理、保護代理、虛擬代理、緩衝代理、智能引用代理等,後面將學習這些不同的代理形式。

2.1、代理模式定義

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

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

2.2、代理模式結構

  代理模式的結構比較簡單,其核心是代理類,爲了讓客戶端能夠一致性地對待真實對象和代對象,在代理模式中引入了抽象層,代理模式結構如圖2.2-1所示:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-JvKIRwpr-1591852097670)(./images/model_proxy.png)]
圖 2.2-1 代理模式結構圖

2.3、代理模式結構圖中角色

  由圖2.2-1可知,代理模式包含如下三個角色

  • Subject(抽象主題角色):它聲明瞭真實主題和代理主題的共同接口,這樣一來在任何使用真實主題的地方都可以使用代理主題,客戶端通常需要針對抽象主題角色進行編程。
  • Proxy(代理主題角色):它包含了對真實主題角色的引用,從而可以在任何時候操作真實主題對象;在代理主題角色中提供 一個與真是主題角色相同的接口,以便在任何時候都可以替代真實主題;代理主題角色還可以控制對真是主題的使用,負責在需要的時候創建和刪除真實主題對象,並對真實主題對象的使用加以約束。通常,在代理主題角色中,客戶端在調用所引用的真實主題操作之前或之後還需要執行其他操作,而不僅僅是單純調用真實主題對象中的操作。
  • RealSubject(真實主題角色):它定義了代理角色所代表的真實對象,在真實主題角色中實現了真實的業務操作,客戶端可以通過代理主題角色間接調用真實主題角色中定義的操作。

2.4、代理模式實現

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

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

abstract class Subject {
	public abstract void Request();
}

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

class RealSubject : Subject	{
	public override void Request() {
		//業務方法具體實現代碼
	}
}

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

class Proxy : Subject
{
	private RealSubject realSubject = new RealSubject(); //維持一個對真實主題對象的引用
	public void PreRequest()
	{
	…...
	}
	public override void Request()
	{
	PreRequest();
	realSubject.Request(); //調用真實主題對象的方法
	PostRequest();
	}
	public void PostRequest()
	{
	……
	}
}

3、常用代理模式分類和說明

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

3.1、遠程代理(Remote Proxy)

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

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

  遠程代理示意圖如圖3.1-1所示,客戶端對象不能直接訪問遠程主機中的業務對象,只能採取間接訪問的方式。遠程業務對象在本地主機中有一個代理對象,該代理對象負責對遠程業務對象的訪問和網絡通信,它對於客戶端對象而言是透明的。客戶端無須關心實現具體業務的是誰,只需要按照服務接口所定義的方式直接與本地主機中的代理對象交互即可。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-BZrwLA6h-1591852097671)(./images/remotProxy.png)]
圖3.1-1 遠程代理示意圖

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

3.2、虛擬代理(Virtual Proxy)

  • (2) 虛擬代理(Virtual Proxy):如果需要創建一個資源消耗較大的對象,先創建一個消耗相對較小的對象來表示,真實對象只在需要時纔會被真正創建。(3) 保護代理(Protect Proxy):控制對一個對象的訪問,可以給不同的用戶提供不同級別的使用權限。

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

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

  • (1) 由於對象本身的複雜性或者網絡等原因導致一個對象需要較長的加載時間,此時可以用一個加載時間相對較短的代理對象來代表真實對象。通常在實現時可以結合多線程技術,一個線程用於顯示代理對象,其他線程用於加載真實對象。這種虛擬代理模式可以應用在程序啓動的時候,由於創建代理對象在時間和處理複雜度上要少於創建真實對象,因此,在程序啓動時,可以用代理對象代替真實對象初始化,大大加速了系統的啓動時間。當需要使用真實對象時,再通過代理對象來引用,而此時真實對象可能已經成功加載完畢,可以縮短用戶的等待時間。
  • (2) 當一個對象的加載十分耗費系統資源的時候,也非常適合使用虛擬代理。虛擬代理可以讓那些佔用大量內存或處理起來非常複雜的對象推遲到使用它們的時候才創建,而在此之前用一個相對來說佔用資源較少的代理對象來代表真實對象,再通過代理對象來引用真實對象。爲了節省內存,在第一次引用真實對象時再創建對象,並且該對象可被多次重用,在以後每次訪問時需要檢測所需對象是否已經被創建,因此在訪問該對象時需要進行存在性檢測,這需要消耗一定的系統時間,但是可以節省內存空間,這是一種用時間換取空間的做法。

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

3.3、緩衝代理(Cache Proxy)

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

緩衝代理(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()方法,同時增加了緩存機制。如圖3.3-1所示:
在這裏插入圖片描述

圖3.3-1 PetShop4.0緩存代理示意圖

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

public static class ProductDataProxy
{
private static readonly int productTimeout = int.Parse(ConfigurationManager.AppSettings ["ProductCacheDuration"]);
private static readonly bool enableCaching = bool.Parse(ConfigurationManager. AppSettings["EnableCaching"]);
public static IList GetProductsByCategory(string category)
{
Product product = new Product();
//如果緩存被禁用,則直接通過product對象來獲取數據
if (!enableCaching)
{
return product.GetProductsByCategory(category);
}
string key = "product_by_category_" + category;
//從緩存中獲取數據
IList data = (IList )HttpRuntime.Cache[key];
//如果緩存中沒有數據則執行如下代碼
if (data == null)
{
data = product.GetProductsByCategory(category);
//通過工廠創建AggregateCacheDependency對象
AggregateCacheDependency cd = DependencyFacade.GetProductDependency ();
//將數據存儲在緩存中,並添加必要的AggregateCacheDependency對象
HttpRuntime.Cache.Add(key, data, cd, DateTime.Now.AddHours(product Timeout), Cache.NoSlidingExpiration, CacheItemPriority.High, null);
}
return data;
}
……
}

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

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

3.4、智能引用代理(Smart Reference Proxy)

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

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

4、代理模式應用實例

4.1、實例說明

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

  • (1) 在進行商務信息查詢之前用戶需要通過身份驗證,只有合法用戶才能夠使用該查詢系統;
  • (2) 在進行商務信息查詢時系統需要記錄查詢日誌,以便根據查詢次數收取查詢費用。

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

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

4.2、實例分析及類圖

 &esmp;通過分析,可以採用一種間接訪問的方式來實現該商務信息查詢系統的設計,在客戶端對象和信息查詢對象之間增加一個代理對象,讓代理對象來實現身份驗證和日誌記錄等功能,而無須直接對原有的商務信息查詢對象進行修改,如圖4.2-1所示:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-GjxpOx73-1591852097674)(./images/商務信息系統.png)]
圖4.2-1 商務信息系統設計方案示意圖

  在圖4.2-1中,客戶端對象通過代理對象間接訪問具有商務信息查詢功能的真實對象,在代理對象中除了調用真實對象的商務信息查詢功能外,還增加了身份驗證和日誌記錄等功能。使用代理模式設計該商務信息查詢系統,結構圖如圖4.2-2所示。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-bZ1ph4fa-1591852097676)(./images/商務信息系統結構圖.png)]
圖4.2-2 商務信息查詢系統結構圖

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

4.3、代碼實現

  • AccessValidator類代碼4.3-1:身份驗證類-業務類

      package proxy;
    
      //模擬實現登錄驗證
      public class AccessValidator {
      	public boolean validate(String userId) {
      		System.out.println("在數據庫中驗證用戶" + userId + "是否是合法");
      		if(userId.equals("楊過")) {
      			System.out.println(userId + "登錄成功!");
      			return true;
      		} else {
      			System.out.println(userId + "登錄失敗!");
      			return false;
      		}
      	}
      }
    
  • Logger類代碼4.3-2:日誌記錄類-業務類

      package proxy;
    
      // 日誌記錄類,業務類,它提供方法log()來保存日誌
      public class Logger {
      	//模擬實現日誌記錄
      	public void log(String userId) {
      		System.out.println("更新數據庫,用戶"+ userId +"查詢次數加1!");
      	}
      }
    
  • Searcher類代碼4.3-3:抽象查詢類-抽象主題角色

      package proxy;
    
      // 抽象查詢類,充當抽象主題角色,它聲明瞭doSearch()方法。
      public interface Searcher {
      	public String doSearch(String userId, String keywords);
      }
    
  • RealSearcher類代碼4.3-4:具體查詢類-真實主題角色

      package proxy;
    
      // 具體查詢類,充當真實主題角色,它實現查詢功能,提供方法doSearch()來查詢信息。
      public class RealSearcher implements Searcher{
    
      	// //模擬查詢商務信息
      	@Override
      	public String doSearch(String userId, String keywords) {
      		System.out.println("用戶" + userId + "使用關鍵詞"+ keywords +"查詢商務信息!");
      		return "返回具體內容";
      	}
      }
    
  • ProxySearcher類代碼4.3-5:代理查詢類-代理主題角色

      package proxy;
    
      public class ProxySearcher implements Searcher{
      	private RealSearcher searcher = new RealSearcher(); //維持一個對真實主題的引用
      	private AccessValidator validator;
      	private Logger logger;
    
      	@Override
      	public String doSearch(String userId, String keyword)
      	{
      		//如果身份驗證成功,則執行查詢
      		if (this.validate(userId)) {
      			String result = searcher.doSearch(userId, keyword); //調用真實主題對象的查詢方法
      			this.log(userId); //記錄查詢日誌
      			return result; //返回查詢結果
      		} else {
      			return null;
      		}
      	}
    
      	//創建訪問驗證對象並調用其Validate()方法實現身份驗證
      	public boolean validate(String userId) {
      		validator = new AccessValidator();
      		return validator.validate(userId);
      	}
      	//創建日誌記錄對象並調用其Log()方法實現日誌記錄
      	public void log(String userId) {
      		logger = new Logger();
      		logger.log(userId);
      	}
      }
    
  • Utils類代碼4.3-6:工具類-獲取具體查詢類對象

      package proxy;
    
      import java.util.Properties;
    
      public class Utils {
      	public static Searcher getProxySearcher() {
      		try {
      			Properties prop = new Properties();
      			prop.load(Utils.class.getClassLoader().getResourceAsStream("proxy.properties"));
      			String className = prop.getProperty("className");
      			return (Searcher) (Class.forName(className).newInstance());
      		}catch (Exception e) {
      			e.printStackTrace();
      			return null;
      		}
      	}
      }
    
  • Client類代碼4.3-7:客戶端類-測試類

      package proxy;
    
      public class Client {
      	public static void main(String[] args) {
      		// 讀取配置文件,獲取代理對象
      		Searcher proxy = Utils.getProxySearcher();
      		String result = proxy.doSearch("楊過", "玉女心經");
    
      		System.out.println(result);
      	}
      }
    
  • 測試結果:

      在數據庫中驗證用戶楊過是否是合法
      楊過登錄成功!
      用戶楊過使用關鍵詞玉女心經查詢商務信息!
      更新數據庫,用戶楊過查詢次數加1!
      返回具體內容
    

4.4、結果及分析

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

5、總結

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

5.1、優缺點

  • 主要優點

    • 共同優點
      • (1)能夠協調調用者和被調用者,在一定程度上降低了系統的耦合度
      • (2)客戶端可以針對抽象主題角色進行編程,增加和更換代理類無須修改源代碼,符合“開閉原則”,系統具有較好的靈活性和可擴展性。
    • 不同類型代理獨特優點
      • (1) 遠程代理爲位於兩個不同地址空間對象的訪問提供了一種實現機制,可以將一些消耗資源較多的對象和操作移至性能更好的計算機上,提高系統的整體運行效率。
      • (2) 虛擬代理通過一個消耗資源較少的對象來代表一個消耗資源較多的對象,可以在一定程度上節省系統的運行開銷。
      • (3) 緩衝代理爲某一個操作的結果提供臨時的緩存存儲空間,以便在後續使用中能夠共享這些結果,優化系統性能,縮短執行時間。
      • (4) 保護代理可以控制對一個對象的訪問權限,爲不同用戶提供不同級別的使用權限。
  • 主要缺點

    • (1) 由於在客戶端和真實主題之間增加了代理對象,因此有些類型的代理模式可能會造成請求的處理速度變慢,例如保護代理。
    • (2) 實現代理模式需要額外的工作,而且有些代理模式的實現過程較爲複雜,例如遠程代理。

5.2、適用場景

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

  • (1) 當客戶端對象需要訪問遠程主機中的對象時可以使用遠程代理。
  • (2) 當需要用一個消耗資源較少的對象來代表一個消耗資源較多的對象,從而降低系統開銷、縮短運行時間時可以使用虛擬代理,例如一個對象需要很長時間才能完成加載時。
  • (3) 當需要爲某一個被頻繁訪問的操作結果提供一個臨時存儲空間,以供多個客戶端共享訪問這些結果時可以使用緩衝代理。通過使用緩衝代理,系統無須在客戶端每一次訪問時都重新執行操作,只需直接從臨時緩衝區獲取操作結果即可。
  • (4) 當需要控制對一個對象的訪問,爲不同用戶提供不同級別的訪問權限時可以使用保護代理。
  • (5) 當需要爲一個對象的訪問(引用)提供一些額外的操作時可以使用智能引用代理。

後記

  參考文獻:Java設計模式(劉偉).pdf。持續更新,歡迎交流,本人QQ:806797785

前端項目源代碼地址:https://gitee.com/gaogzhen/vue-leyou
後端JAVA源代碼地址:https://gitee.com/gaogzhen/JAVA
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章