設計模式--代理模式Proxy(結構型)

benbenxiongyuan發表於2014-04-10

1.概述

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

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

例子2:可以呼叫遠端代理處理一些操作如圖:

2.問題:

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

3.解決方案

代理模式: 為其他物件提供一種代理,並以控制對這個物件的訪問。(Provide asurrogate or placeholder foranotherobject 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();  
<?php
/** 
* 代理模式 
* 
* 為其他物件提供一個代理以控制這個物件的訪問 
* 
*/ 
/**
 *  抽象主題角色(Subject):天氣
 *
 */
interface Weather
{
	public function request($city);
	public function display($city);
	public function isValidCity($city);

}

/**
 * 真實主題角色(RealSubject):
 *
 */
class RealWeather implements Weather 
{
	protected $_url = 'http://www.google.com/ig/api?&oe=utf-8&hl=zh-cn&weather=';
	protected $_weatherXml = '' ;
	function __construct(){
		
	}

	public function request($city){
		$this->_weatherXml = file_get_contents($this->_url . $city );
	}
	public function display($city ){
		if ($this->_weatherXml == '') {
			$this->request($city);
		}
		//$this->_weatherXml = mb_convert_encoding($this->_weatherXml, 'UTF-8', 'GB2312');
		$weatherxml = simplexml_load_string($this->_weatherXml);
		$low = intval($weatherxml->weather->forecast_conditions[0]->low->attributes());
		$high = $weatherxml->weather->forecast_conditions[0]->high->attributes();
		$icon= 'http://www.google.com'. $weatherxml->weather->forecast_conditions[0]->icon->attributes();
		$condition=$weatherxml->weather->forecast_conditions[0]->condition->attributes();
		$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;
		echo  $weather;
	}
	
	public function isValidCity($city){
		
	}

}

/**
 * 代理角色(Proxy):延遲代理 
 *
 */
class ProxyWeather  implements Weather {
	private $_client ;
	private function client() {
		if (! $this->_client instanceof RealWeather) {
			$this->_client = new RealWeather();
		}
		return $this->_client;

	}
	public function request($city){
		$this->_client()->request($city);
	}

	public function isValidCity($city) {
		return $this->_client()->isValidCity($city);
	}


	public function display($city) {
		return $this->client()->display($city);
	}
}
/**
 * 代理角色(Proxy):動態代理
 *
 */
class GenericProxyWeather {

	protected $_subject;
	public function __construct($subject) {
		$this->_subject = $subject;
	}

	public function __call($method, $args) {
		return call_user_func_array(
		array($this->_subject, $method),
		$args);
	}

}



class Client{
	
	static function main(){
		$proxy = new ProxyWeather();
		$report = $proxy->display('beijing');
	}
	static function Genericmain(){
		$proxy = new GenericProxyWeather(new RealWeather());
		$report = $proxy->display('beijing');
	}
}
header('Content-type:text/html;charset=UTF-8');
Client::main();
//Client::Genericmain();

 

9. 與其他相關模式

1)介面卡模式Adapter:介面卡Adapter 為它所適配的物件提供了一個不同的介面。相反,代理提供了與它的實體相同的介面。

然而,用於訪問保護的代理可能會拒絕執行實體會執行的操作,因此,它的介面實際上可能只是實體介面的一個子集。

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

 

10.總結

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

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

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

3、proxy並不一定要求保持介面的一致性,只要能夠實現間接控制,有時候損及一些透明性是可以接受的。例如上面的那個例子,

代理型別ProxyClass和被代理型別LongDistanceClass可以不用繼承自同一個介面,

正像GoF《設計模式》中說的:為其他物件提供一種代理以控制這個物件的訪問。代理型別從某種角度上講也可以起到控制被代理型別的訪問的作用。

 


轉自:http://blog.csdn.net/hguisu/article/details/7542143


相關文章