結構型模7:代理模式(Proxy Pattern)

1.概述

       因爲某個對象消耗太多資源,而且你的代碼並不是每個邏輯路徑都需要此對象, 你曾有過延遲創建對象的想法嗎 ( if和else就是不同的兩條邏輯路徑) ? 你有想過限制訪問某個對象,也就是說,提供一組方法給普通用戶,特別方法給管理員用戶?以上兩種需求都非常類似,並且都需要解決一個更大的問題:你如何提供一致的接口給某個對象讓它可以改變其內部功能,或者是從來不存在的功能? 可以通過引入一個新的對象,來實現對真實對象的操作或者將新的對象作爲真實對象的一個替身。即代理對象。它可以在客戶端和目標對象之間起到中介的作用,並且可以通過代理對象去掉客戶不能看到的內容和服務或者添加客戶需要的額外服務

例子1:經典例子就是網絡代理,你想訪問facebook或者twitter ,如何繞過GFW,找個代理網站。

例子2:可以調用遠程代理處理一些操作如圖:

2.問題:

你怎樣才能在不直接操作對象的情況下,對此對象進行訪問?

3.解決方案

代理模式: 爲其他對象提供一種代理,並以控制對這個對象的訪問。(Provide asurrogate or placeholderforanother object tocontrol access to it. )而對一個對象進行訪問控制的一個原因是爲了只有在我們確實需要這個對象時纔對它進行創建和初始化。它是給某一個對象提供一個替代者(佔位者),使之在client對象和subject對象之間編碼更有效率。代理可以提供延遲實例化(lazy instantiation),控制訪問, 等等,包括只在調用中傳遞。 一個處理純本地資源的代理有時被稱作虛擬代理。遠程服務的代理常常稱爲遠程代理。強制 控制訪問的代理稱爲保護代理。

4.實用性

在需要用比較通用和複雜的對象指針代替簡單的指針的時候,使用 Proxy模式。下面是一些可以使用Proxy模式常見情況:
1) 遠程代理(Remote  Proxy)爲一個位於不同的地址空間的對象提供一個本地的代理對象。這個不同的地址空間可以是在同一臺主機中,也可是在另一臺主機中,遠程代理又叫做大使(Ambassador)
2) 虛擬代理(Virtual Proxy)根據需要創建開銷很大的對象。如果需要創建一個資源消耗較大的對象,先創建一個消耗相對較小的對象來表示,真實對象只在需要時纔會被真正創建。 
3) 保護代理(Protection Proxy)控制對原始對象的訪問。保護代理用於對象應該有不同的訪問權限的時候。
4) 智能指引(Smart Reference)取代了簡單的指針,它在訪問對象時執行一些附加操作。
5) Copy-on-Write代理:它是虛擬代理的一種,把複製(克隆)操作延遲到只有在客戶端真正需要時才執行。一般來說,對象的深克隆是一個開銷較大的操作,Copy-on-Write代理可以讓這個操作延遲,只有對象被用到的時候才被克隆。

5. 結構

Uml圖:


簡單結構示意圖:



6.模式的組成
1)代理角色(Proxy):
. 保存一個引用使得代理可以訪問實體。若 RealSubject和Subject的接口相同,Proxy會引用Subject。
. 提供一個與Subject的接口相同的接口,這樣代理就可以用來替代實體。
. 控制對實體的存取,並可能負責創建和刪除它。
. 其他功能依賴於代理的類型:
• Remote Proxy負責對請求及其參數進行編碼,並向不同地址空間中的實體發送已編碼的請求。
• Virtual Proxy可以緩存實體的附加信息,以便延遲對它的訪問。
• Protection Proxy檢查調用者是否具有實現一個請求所必需的訪問權限。
2) 抽象主題角色(Subject):定義真實主題角色RealSubject 和 抽象主題角色Proxy的共用接口,這樣就在任何使用RealSubject的地方都可以使
用Proxy。代理主題通過持有真實主題RealSubject的引用,不但可以控制真實主題RealSubject的創建或刪除,可以在真實主題RealSubject被調用前進行攔截,或在調用後進行某些操作. 

3) 真實主題角色(RealSubject):定義了代理角色(proxy)所代表的具體對象. 

7. 效果

Proxy模式在訪問對象時引入了一定程度的間接性。根據代理的類型,附加的間接性有多種用途:
1) Remote Proxy可以隱藏一個對象存在於不同地址空間的事實。也使得客戶端可以訪問在遠程機器上的對象,遠程機器可能具有更好的計算性能與處理速度,可以快速響應並處理客戶端請求。
2) Virtual Proxy 可以進行最優化,例如根據要求創建對象。即通過使用一個小對象來代表一個大對象,可以減少系統資源的消耗。
3) Protection Proxies和Smart Reference都允許在訪問一個對象時有一些附加的內務處理(Housekeeping task) 。

Proxy模式還可以對用戶隱藏另一種稱之爲寫時複製(copy-on-write)的優化方式,該優化與根據需要創建對象有關。拷貝一個龐大而複雜的對象是一種開銷很大的操作,如果這個拷貝根本沒有被修改,那麼這些開銷就沒有必要。用代理延遲這一拷貝過程,我們可以保證只有當這個對象被修改的時候纔對它進行拷貝。在實現copy-on-write時必須對實體進行引用計數。拷貝代理僅會增加引用計數。只有當用戶請求一個修改該實體的操作時,代理纔會真正的拷貝它。在這種情況下,代理還必須減
少實體的引用計數。當引用的數目爲零時,這個實體將被刪除。copy-on-write可以大幅度的降低拷貝龐大實體時的開銷。

代理模式能夠協調調用者和被調用者,在一定程度上降低了系統的耦合度。

代理模式的缺點
由於在客戶端和真實主題之間增加了代理對象,因此有些類型的代理模式可能會造成請求的處理速度變慢。
實現代理模式需要額外的工作,有些代理模式的實現非常複雜。

8.實現

我們用獲取天氣預報的例子說明代理模式:

  1. <?php  
  2. /**  
  3. * 代理模式  
  4.  
  5. * 爲其他對象提供一個代理以控制這個對象的訪問  
  6.  
  7. */   
  8. /** 
  9.  *  抽象主題角色(Subject):天氣 
  10.  * 
  11.  */  
  12. interface Weather  
  13. {  
  14.     public function request($city);  
  15.     public function display($city);  
  16.     public function isValidCity($city);  
  17.   
  18. }  
  19.   
  20. /** 
  21.  * 真實主題角色(RealSubject): 
  22.  * 
  23.  */  
  24. class RealWeather implements Weather   
  25. {  
  26.     protected $_url = 'http://www.google.com/ig/api?&oe=utf-8&hl=zh-cn&weather=';  
  27.     protected $_weatherXml = '' ;  
  28.     function __construct(){  
  29.           
  30.     }  
  31.   
  32.     public function request($city){  
  33.         $this->_weatherXml = file_get_contents($this->_url . $city );  
  34.     }  
  35.     public function display($city ){  
  36.         if ($this->_weatherXml == '') {  
  37.             $this->request($city);  
  38.         }  
  39.         //$this->_weatherXml = mb_convert_encoding($this->_weatherXml, 'UTF-8', 'GB2312');  
  40.         $weatherxml = simplexml_load_string($this->_weatherXml);  
  41.         $low = intval($weatherxml->weather->forecast_conditions[0]->low->attributes());  
  42.         $high = $weatherxml->weather->forecast_conditions[0]->high->attributes();  
  43.         $icon'http://www.google.com'$weatherxml->weather->forecast_conditions[0]->icon->attributes();  
  44.         $condition=$weatherxml->weather->forecast_conditions[0]->condition->attributes();  
  45.         $weather = date('Y年n月j日').'  天氣預報:<span class="cor_ff6c00 f_bold">'.$city_names[$city].' </span>  <img class="v_middle" src="'.$icon.'" alt="'.$condition.'" width="16" height="17" align="absmiddle" /> <span class="f_bold"></span>:    '.$low.'°C ~ '.$high.'°C '.$condition;  
  46.         echo  $weather;  
  47.     }  
  48.       
  49.     public function isValidCity($city){  
  50.           
  51.     }  
  52.   
  53. }  
  54.   
  55. /** 
  56.  * 代理角色(Proxy):延遲代理  
  57.  * 
  58.  */  
  59. class ProxyWeather  implements Weather {  
  60.     private $_client ;  
  61.     private function client() {  
  62.         if (! $this->_client instanceof RealWeather) {  
  63.             $this->_client = new RealWeather();  
  64.         }  
  65.         return $this->_client;  
  66.   
  67.     }  
  68.     public function request($city){  
  69.         $this->_client()->request($city);  
  70.     }  
  71.   
  72.     public function isValidCity($city) {  
  73.         return $this->_client()->isValidCity($city);  
  74.     }  
  75.   
  76.   
  77.     public function display($city) {  
  78.         return $this->client()->display($city);  
  79.     }  
  80. }  
  81. /** 
  82.  * 代理角色(Proxy):動態代理 
  83.  * 
  84.  */  
  85. class GenericProxyWeather {  
  86.   
  87.     protected $_subject;  
  88.     public function __construct($subject) {  
  89.         $this->_subject = $subject;  
  90.     }  
  91.   
  92.     public function __call($method$args) {  
  93.         return call_user_func_array(  
  94.         array($this->_subject, $method),  
  95.         $args);  
  96.     }  
  97.   
  98. }  
  99.   
  100.   
  101.   
  102. class Client{  
  103.       
  104.     static function main(){  
  105.         $proxy = new ProxyWeather();  
  106.         $report = $proxy->display('beijing');  
  107.     }  
  108.     static function Genericmain(){  
  109.         $proxy = new GenericProxyWeather(new RealWeather());  
  110.         $report = $proxy->display('beijing');  
  111.     }  
  112. }  
  113. header('Content-type:text/html;charset=UTF-8');  
  114. Client::main();  
  115. //Client::Genericmain();  

9. 與其他相關模式

1)適配器模式Adapter適配器Adapter 爲它所適配的對象提供了一個不同的接口。相反,代理提供了與它的實體相同的接口。然而,用於訪問保護的代理可能會拒絕執行實體會執行的操作,因此,它的接口實際上可能只是實體接口的一個子集。

2) 裝飾器模式Decorator:儘管Decorator的實現部分與代理相似,但 Decorator的目的不一樣。Decorator爲對象添加一個或多個功能,而代理則控制對對象的訪問。

10.總結

代理模式在很多情況下都非常有用,特別是你想強行控制一個對象的時候,比如:延遲加載,監視狀態變更的方法等等

 1、“增加一層間接層”是軟件系統中對許多負責問題的一種常見解決方法。在面向對象系統中,直接使用某些對象會帶來很多問題,作爲間接層的proxy對象便是解決這一問題的常用手段。

2、具體proxy設計模式的實現方法、實現粒度都相差很大,有些可能對單個對象作細粒度的控制,有些可能對組件模塊提供抽象代理層,在架構層次對對象作proxy。

3、proxy並不一定要求保持接口的一致性,只要能夠實現間接控制,有時候損及一些透明性是可以接受的。例如上面的那個例子,代理類型ProxyClass和被代理類型LongDistanceClass可以不用繼承自同一個接口,正像GoF《設計模式》中說的:爲其他對象提供一種代理以控制這個對象的訪問。代理類型從某種角度上講也可以起到控制被代理類型的訪問的作用。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章