熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> .NET編程 >> 正文

C# Design Patterns (4) - Proxy

2022-06-13   來源: .NET編程 

  Proxy Pattern (代理模式)

  The Proxy Pattern provides a surrogate or placeholder for another object to control access to it

  - Design Patterns: Elements of Reusable ObjectOriented Software

  在 GoF 的書中對 Proxy 模式的定義為替某個對象提供一個替身以控制外界對這個對象的訪問而這個被替身代理的對象 (被代理者)可能是遠端的對象創建時需要高成本或大計算量的對象或需要安全控制的對象

  

  圖  此圖為 Proxy 模式的經典類圖

  _Shell / Programcs

  using System;

  namespace __Shell

  {

  //用戶端程序

  class Program

  {

  static void Main(string[] args)

  {

  //用戶把 Proxy (代理者) 視為 RealSubject (真實的對象) 來操作

  Proxy proxy = new Proxy();

  proxyRequest();

  ConsoleRead();

  }

  }

  //代理者被代理者共同實現的接口

  abstract class Subject

  {

  public abstract void Request();

  }

  //被代理者真實的對象把復雜性封裝在此

  class RealSubject : Subject

  {

  //真正做事的方法把復雜性封裝在此用戶無須知道細節

  public override void Request()

  {

  ConsoleWriteLine(真實的請求);

  }

  }

  //代理者

  class Proxy : Subject

  {

  RealSubject realSubject;

  public override void Request()

  {

  if (realSubject == null)

  {

  realSubject = new RealSubject();

  }

  realSubjectRequest();

  }

  }

  }

  /*

  執行結果

  真實的請求

  */

  上方圖 的 Class Diagram以及「Shell (殼)」示例中我們有一個 Subject 抽像類這是 RealSubject 和 Proxy 共同的接口好讓任何用戶都可將 Proxy 對象 (代理者) 視為 RealSubject 對象 (被代理者亦即真實的對象) 來處理

  其中 RealSubject 是真正做事的對象它是被 Proxy 代理的對象它的方法是真正做事的函數並會將一些復雜的工作封裝在其方法裡而無須讓客戶端程序知道實現細節為何而 Proxy 中的同名稱方法 (Request 方法)則可做一些邏輯判斷比如上例中我們做了 realSubject 是否為 null 的 if 判斷亦即只有客戶程序第一次調用此函數時才去創建 RealSubject 對象

  客戶端和 RealSubject 的互動都必須透過 Proxy也由於 Proxy 和 RealSubject 實現了相同的接口所以客戶在任何需要 RealSubject 的地方都可以用 Proxy 取而代之此外Proxy 也控制了客戶端對 RealSubject 的訪問其目的如同本帖一開始所說的因為 RealSubject 可能是網絡上遠端機器上的對象創建時需要高成本或需要安全控管及經過認證才能訪問的對象其原理類似於下圖 的 HTTP Proxy Server 情景Clientside 想要前往 Internet 取得 Web Server 上的信息時可透過 Proxy Server 幫忙處理其中 Web Server 就如同 RealSubject (被代理者)而 Proxy Server 就如同 Proxy (代理者)

  

  圖  基於代理的遠程訪問示意圖

  Proxy Pattern 依照功能和目的運行環境的不同可概略分成以下幾種

  遠程代理 (Remote Proxy)替網絡上機器與機器之間的請求 (request)做「發送 / 接收」和編碼加密…等工作讓用戶端程序只要調用這個代理就能做遠端調用Java RMIWeb ServiceNET RemotingNET WCF其中 Web Service 和 WCF會在引用的客戶端程序中產生 App_WebReferences 文件夾一些檔案和代理類這些檔案即為此種遠程代理

  保護代理 (Protection Proxy; Access Proxy)檢查調用者是否有權限去訪問真實的對象例如用戶是否有輸入正確的密碼以通過認證

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

  虛擬代理 (Virtual Proxy)讓一個資源消耗較大的對象只有在需要時才會真正被創建或讓真實對象只有在第一次被調用時才創建

  其他例如CopyOnWrite ProxyCache ProxyFirewall ProxySynchronization Proxy等 [] [] []

  在 GoF 中所舉的例子是 Virtual Proxy舉了一個文檔中內嵌圖片的范例 [] []若圖片是在文檔 (如PDFPowerPoint) 的其中某一頁用戶剛打開文檔時並不需要載入圖片可先用一個 ImageProxy代替真實的圖片被載入當用戶滾動滾動條轉到文檔特定的頁數時才真正從硬盤載入圖片以求開啟此文檔時能加快速度讓用戶對此軟件有較好的體驗

  

  圖  虛擬代理 (Virtual Proxy) 在 GoF 示例的類圖與本帖圖 的原理相同

  如上圖 及下方代碼所示當文檔被開啟時ImageProxy 對象會代替 (代理) Image 對象被載入在用戶還沒轉到圖片所在的頁數時也就是還沒調用 ImageProxy 的 draw 方法時圖片並不會被載入因此可加速文檔的開啟節省內存的使用當用戶轉到圖片所在頁數時ImageProxy 的 draw 方法才會被調用此時才真正去創建 Image 對象從硬盤中載入圖片在此例的 draw 方法裡我們實現了「虛擬代理」只有在方法「第一次」被調用時才創建資源消耗大的 Image 對象以節省內存控制創建成本昂貴的資源

  _VirtualProxy_Image / Programcs

  using System;

  using blogsWizardWuSample;

  //客戶端程序

  class Program

  {

  static void Main(string[] args)

  {

  //當文檔被打開時ImageProxy 對象會代替(代理) Image 對象被載入

  IGraphic image = new ImageProxy(風景圖jpg);

  ConsoleWriteLine(文檔被打開真實的圖片尚未被載入);

  ConsoleReadLine();

  ConsoleWriteLine(用戶已把滾動條滾到特定頁數圖片此時才從硬盤載入);

  imagedraw();          //真正要顯示圖片了

  ConsoleReadLine();

  }

  }

  //服務器端程序

  namespace blogsWizardWuSample

  {

  //此為「代理者被代理者」共同的接口

  public interface IGraphic

  {

  //用來畫圖的方法

  void draw();

  }

  //被代理者真實的對象把復雜性封裝在此

  public class Image : IGraphic

  {

  private byte[] data;

  public Image(String fileName)             //構造函數

  {

  //載入圖片把復雜性封裝起來

  //data = loadImage(fileName);

  ConsoleWriteLine(開始載入圖片);

  }

  public void draw()

  {

  //繪制圖片在屏幕上把復雜性封裝起來

  //drawToScreen(data);

  ConsoleWriteLine(圖片已成功繪制在屏幕上);

  }

  }

  //代理者

  public class ImageProxy : IGraphic

  {

  private String fileName;

  private Image image;

  public ImageProxy(String filename)     //構造函數

  {

  thisfileName = filename;

  image = null;

  }

  //等到真正要顯示圖片了這個 ImageProxy 的 draw 方法才會被調用

  //此時才會真正去創建 Image 對象並從硬盤中載入圖片

  public void draw()

  {

  //虛擬代理僅在方法第一次被調用時才創建資源消耗大的 Image 對象

  if (image == null)

  {

  image = new Image(fileName);

  }

  //實際去繪制圖片在螢幕上

  imagedraw();

  }

  }

  } // end of namespace

  /*

  執行結果

  文檔被開啟真實的圖片尚未被載入

  用戶已把滾動條滾到特定頁數圖片此時才從硬盤載入

  開始載入圖片

  圖片已成功繪制在屏幕上

  */

  代理的主要目的之一是把復雜性封裝起來讓客戶端程序在引用上更容易而不需要顧慮藏在身後這些復雜的邏輯如上方這個示例我們可以把「載入圖片繪制圖片在屏幕上」這些較復雜的 NET API 引用代碼都封裝在「被代理者」這個真實 Image 對象的幾個自定義方法裡

  此外我們也可用相同於本示例的邏輯去實現「保護代理 (Protection Proxy)」的觀念例如要求用戶必須輸入正確的密碼先通過認證 (Authentication) 後才能訪問 RealSubject 對象調用其方法相關的范例有興趣的讀者可自行去「C# Design Patterns」這本書籍的 OReilly 英文官方網站上 []下載第二章的源代碼

  接下來的第三個示例為「數據訪問代理」的 ASPNET 例子 [] []用來取得 Northwind 數據庫中 Employees 表的記錄總數其類圖如下圖 和本帖第一第二個示例的類圖略有不同它是將 RealSubjectProxy 合而為一變成單一個 DbCommandProxy 類其左側的 DbContext 類只是用來協助解決復雜性的問題包括取得 nfig 的數據庫連接字符串建立數據庫的連接

  這個 DbCommandProxy 類實現了 NET 用來執行 SQL 語句的原生 IDbCommand 接口 []我們為了將復雜性問題具體的數據庫連接方式隔離出來因此另外提供了一個 DbContext 類並將這些動作都搬移至 DbContext 類去處理以另一種設計理念實現了 Proxy Pattern

  

  圖  示例 _DbProxy / Defaultaspxcs 的類圖

  _DbProxy / Defaultaspxcs

  using System;

  using blogsWizardWuSample;

  using SystemData;

  using SystemDataCommon;

  using SystemConfiguration;

  //客戶端程序

  public partial class _Default : SystemWebUIPage

  {

  protected void Page_Load(object sender EventArgs e)

  {

  IDbCommand command = new DbCommandProxy();

  commandCommandText = SELECT COUNT(*) FROM Employees;

  //顯示 Employees 表的記錄總數

  thisLabelText = commandExecuteScalar()ToString();

  }

  }

  //服務器端程序

  namespace blogsWizardWuSample

  {

  //這個類並非「被代理者」此為自定義 DbCommandProxy 類的輔助類

  //將具體建立數據庫連接…等較復雜的部分提取出來隔離在這個類裡去處理

  class DbContext

  {

  private string strProviderName;

  private string strConnectionString;

  public DbContext(string name)        //構造函數

  {

  //取得 nfig 裡的數據庫連接字符串名稱

  ConnectionStringSettings setting = ConfigurationManagerConnectionStrings[ConnString_SqlClient];

  thisstrProviderName = settingProviderName;

  thisstrConnectionString = settingConnectionString;

  }

  public DbConnection CreateConnection()

  {

  DbProviderFactory factory = DbProviderFactoriesGetFactory(thisstrProviderName);

  DbConnection connection = factoryCreateConnection();

  connectionConnectionString = thisstrConnectionString;

  return connection;

  }

  }

  //將「代理者被代理者」合而為一 (另一種做法)

  //此類同時為 RealSubject 和 Proxy 的類

  public class DbCommandProxy : IDbCommand

  {

  private DbContext context;

  private string strCommandText;

  public DbCommandProxy(string name)                 //構造函數

  {

  if (stringIsNullOrEmpty(name)) throw new ArgumentNullException(name);

  ntext = new DbContext(name);

  }

  public DbCommandProxy() : this(default) { }     //構造函數

  public object ExecuteScalar()

  {

  using (DbConnection connection = contextCreateConnection())

  {

  connectionOpen();

  DbCommand command = connectionCreateCommand();

  commandCommandText = thisstrCommandText;

  commandCommandType = CommandTypeText;

  return commandExecuteScalar();

  }

  }

  public string CommandText

  {

  get { return thisstrCommandText; }

  set { thisstrCommandText = value; }

  }

  //IDbCommand  接口未被實現的成員

  }

  } // end of namespace

  

  Proxy Pattern 適用的情景

  對象創建的代價比較高

  僅在操作被請求時創建對象

  對象需要訪問控制如: 權限驗證或訪問的同時去執行檢查或簿記工作

  需要訪問遠程站點

  被訪問時需要執行一些邏輯判斷的動作

  Proxy Pattern 的優點

  降低對象使用的復雜度

  增加對象使用的友好度

  提高程序的效率和性能 (如同 HTTP 的 Proxy Server)

  Proxy Pattern 的缺點

  和一些 Pattern 一樣Proxy Pattern 會造成系統設計中類的數量增加

  Proxy Pattern 的其他特性

  Proxy Pattern 的結構類似上一篇帖子的 Decorator Pattern (裝飾模式)但是目的不同

  Decorator Pattern 替對象加上行為而 Proxy Pattern 則是控制對象的訪問

  Proxy Pattern 的關系是在設計階段就確定好了的是事先知道的而 Decorator Pattern 卻可以動態地添加

  


From:http://tw.wingwit.com/Article/program/net/201311/11289.html
    推薦文章
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.