本帖介绍 Proxy Pattern (代理模式)。

Proxy Pattern (代理模式)

The Proxy Pattern provides a surrogate or placeholder for another object to control access to it...
                                 - Design Patterns: Elements of Reusable Object-Oriented Software

在 GoF 的书中,对 Proxy 模式的定义为:替某个对象,提供一个替身,以控制外界对这个对象的访问。而这个被替身代理的对象 (被代理者),可能是远端的对象、创建时需要高成本或大计算量的对象,或需要安全控制的对象。


图 1 此图为 Proxy 模式的经典类图

01_Shell / Program.cs
using System;

namespace _01_Shell
{
    //用户端程序
    class Program
    {
        static void Main(string[] args)
        {
            //用户把 Proxy (代理者) 视为 RealSubject (真实的对象) 来操作
            Proxy proxy = new Proxy();
            proxy.Request();

Console.Read();
        }
    }

//代理者、被代理者共同实现的接口
    abstract class Subject
    {
        public abstract void Request();
    }

//被代理者。真实的对象,把复杂性封装在此
    class RealSubject : Subject
    {
        //真正做事的方法。把复杂性封装在此,用户无须知道细节
        public override void Request()
        {
            Console.WriteLine("真实的请求");
        }
    }

//代理者
    class Proxy : Subject
    {
        RealSubject realSubject;

public override void Request()
        {
            if (realSubject == null)
            {
                realSubject = new RealSubject();
            }

realSubject.Request();
        }
    }
}

/*
 执行结果:
 
真实的请求

*/

上方图 1 的 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 可能是网络上远端机器上的对象、创建时需要高成本、或需要安全控管及经过认证才能访问的对象。其原理类似于下图 2 的 HTTP Proxy Server 情景,Client-side 想要前往 Internet 取得 Web Server 上的信息时,可透过 Proxy Server 帮忙处理;其中 Web Server 就如同 RealSubject (被代理者),而 Proxy Server 就如同 Proxy (代理者)。


图 2 基于代理的远程访问示意图

Proxy Pattern 依照功能和目的、运行环境的不同,可概略分成以下几种:

  • 远程代理 (Remote Proxy):替网络上机器与机器之间的请求 (request),做「发送 / 接收」和编码、加密…等工作,让用户端程序,只要调用这个代理就能做远端调用,如:Java RMI、Web Service、.NET Remoting、.NET WCF。其中 Web Service 和 WCF,会在引用的客户端程序中,产生 App_WebReferences 文件夹、一些档案和代理类,这些档案即为此种远程代理。
  • 保护代理 (Protection Proxy; Access Proxy):检查调用者是否有权限,去访问真实的对象,例如用户是否有输入正确的密码以通过认证。
  • 智能引用代理 (Smart Reference Proxy):当对象被调用时,提供一些额外的操作,例如:记录对象被调用的次数。
  • 虚拟代理 (Virtual Proxy):让一个资源消耗较大的对象,只有在需要时才会真正被创建,或让真实对象只有在第一次被调用时才创建。
  • 其他,例如:Copy-On-Write Proxy、Cache Proxy、Firewall Proxy、Synchronization Proxy...等 [2], [3], [5]。

在 GoF 中所举的例子是 Virtual Proxy,举了一个文档中内嵌图片的范例 [4], [11]。若图片是在文档 (如:PDF、PowerPoint) 的其中某一页,用户刚打开文档时,并不需要载入图片,可先用一个 ImageProxy,代替真实的图片被载入;当用户滚动滚动条、转到文档特定的页数时,才真正从硬盘载入图片,以求开启此文档时能加快速度,让用户对此软件有较好的体验。


图 3 虚拟代理 (Virtual Proxy) 在 GoF 示例的类图,与本帖图 1 的原理相同

如上图 3 及下方代码所示,当文档被开启时,ImageProxy 对象会代替 (代理) Image 对象被载入,在用户还没转到图片所在的页数时,也就是还没调用 ImageProxy 的 draw 方法时,图片并不会被载入,因此可加速文档的开启、节省内存的使用;当用户转到图片所在页数时,ImageProxy 的 draw 方法才会被调用,此时才真正去创建 Image 对象、从硬盘中载入图片。在此例的 draw 方法里,我们实现了「虚拟代理」,只有在方法「第一次」被调用时,才创建资源消耗大的 Image 对象,以节省内存、控制创建成本昂贵的资源。

02_VirtualProxy_Image / Program.cs
using System;
using com.cnblogs.WizardWu.Sample02;

//客户端程序
class Program
{
    static void Main(string[] args)
    {
        //当文档被打开时,ImageProxy 对象会代替(代理) Image 对象被载入
        IGraphic image1 = new ImageProxy("风景图.jpg");
        Console.WriteLine("文档被打开,真实的图片尚未被载入.");
        Console.ReadLine();

Console.WriteLine("用户已把滚动条滚到特定页数,图片此时才从硬盘载入.");

image1.draw();          //真正要显示图片了

Console.ReadLine();
    }
}

//服務器端程序
namespace com.cnblogs.WizardWu.Sample02
{
    //此为「代理者、被代理者」共同的接口
    public interface IGraphic
    {
        //用来画图的方法
        void draw();
    }

//被代理者。真实的对象,把复杂性封装在此
    public class Image : IGraphic
    {
        private byte[] data;

public Image(String fileName)             //构造函数
        {
            //载入图片。把复杂性封装起来
            //data = loadImage(fileName);
            Console.WriteLine("开始载入图片...");
        }

public void draw()
        {
            //绘制图片在屏幕上。把复杂性封装起来
            //drawToScreen(data);
            Console.WriteLine("图片已成功绘制在屏幕上.");
        }
    }

//代理者
    public class ImageProxy : IGraphic
    {
        private String fileName;
        private Image image;

public ImageProxy(String filename)     //构造函数
        {
            this.fileName = filename;
            image = null;
        }

//等到真正要显示图片了,这个 ImageProxy 的 draw 方法才会被调用,
        //此时才会真正去创建 Image 对象,并从硬盘中载入图片
        public void draw()
        {
            //虚拟代理。仅在方法第一次被调用时,才创建资源消耗大的 Image 对象
            if (image == null)
            {
                image = new Image(fileName);                
            }

//实际去绘制图片在萤幕上
            image.draw();
        }
    }

} // end of namespace

/*
 执行结果:
 
文档被开启,真实的图片尚未被载入.

用户已把滚动条滚到特定页数,图片此时才从硬盘载入.
开始载入图片...
图片已成功绘制在屏幕上.

*/

代理的主要目的之一,是把复杂性封装起来,让客户端程序在引用上更容易,而不需要顾虑藏在身后这些复杂的逻辑。如上方这个示例,我们可以把「载入图片、绘制图片在屏幕上」这些较复杂的 .NET API 引用代码,都封装在「被代理者」这个真实 Image 对象的几个自定义方法里。

此外,我们也可用相同于本示例的逻辑,去实现「保护代理 (Protection Proxy)」的观念,例如要求用户必须输入正确的密码、先通过认证 (Authentication) 后,才能访问 RealSubject 对象、调用其方法。相关的范例,有兴趣的读者可自行去「C# 3.0 Design Patterns」这本书籍的 O'Reilly 英文官方网站上 [9],下载第二章的源代码。

接下来的第三个示例,为「数据访问代理」的 ASP.NET 例子 [10], [12],用来取得 Northwind 数据库中 Employees 表的记录总数。其类图如下图 4,和本帖第一、第二个示例的类图略有不同,它是将 RealSubject、Proxy 合而为一,变成单一个 DbCommandProxy 类;其左侧的 DbContext 类只是用来协助解决复杂性的问题,包括取得 Web.config 的数据库连接字符串、建立数据库的连接。

这个 DbCommandProxy 类,实现了 .NET 用来执行 SQL 语句的原生 IDbCommand 接口 [6]。我们为了将复杂性问题、具体的数据库连接方式隔离出来,因此另外提供了一个 DbContext 类,并将这些动作都搬移至 DbContext 类去处理,以另一种设计理念实现了 Proxy Pattern。


图 4 示例 03_DbProxy / Default.aspx.cs 的类图

03_DbProxy / Default.aspx.cs
using System;

using com.cnblogs.WizardWu.Sample03;
using System.Data;
using System.Data.Common;
using System.Configuration;

//客戶端程序
public partial class _Default : System.Web.UI.Page 
{
    protected void Page_Load(object sender, EventArgs e)
    {
        IDbCommand command = new DbCommandProxy();
        command.CommandText = "SELECT COUNT(*) FROM Employees";

//显示 Employees 表的记录总数
        this.Label1.Text = command.ExecuteScalar().ToString();
    }
}

//服務器端程序
namespace com.cnblogs.WizardWu.Sample03
{
    //这个类并非「被代理者」。此为自定义 DbCommandProxy 类的辅助类。
    //将具体建立数据库连接…等,较复杂的部分提取出来,隔离在这个类里去处理
    class DbContext
    {
        private string strProviderName;
        private string strConnectionString;

public DbContext(string name)        //构造函数
        {
            //取得 Web.config 里的数据库连接字符串名称
            ConnectionStringSettings setting = ConfigurationManager.ConnectionStrings["ConnString_SqlClient"];
            this.strProviderName = setting.ProviderName;
            this.strConnectionString = setting.ConnectionString;
        }

public DbConnection CreateConnection()
        {
            DbProviderFactory factory = DbProviderFactories.GetFactory(this.strProviderName);
            DbConnection connection = factory.CreateConnection();
            connection.ConnectionString = this.strConnectionString;
            return connection;
        }
    }

//将「代理者、被代理者」合而为一 (另一种做法)。
    //此类同时为 RealSubject 和 Proxy 的类
    public class DbCommandProxy : IDbCommand
    {
        private DbContext context;
        private string strCommandText;

public DbCommandProxy(string name)                 //构造函数
        {
            if (string.IsNullOrEmpty(name)) throw new ArgumentNullException("name");
            this.context = new DbContext(name);
        }
        public DbCommandProxy() : this("default") { }     //构造函数

public object ExecuteScalar()
        {
            using (DbConnection connection = context.CreateConnection())
            {
                connection.Open();
                DbCommand command = connection.CreateCommand();
                command.CommandText = this.strCommandText;
                command.CommandType = CommandType.Text;
                return command.ExecuteScalar();
            }
        }

public string CommandText
        {
            get { return this.strCommandText; }
            set { this.strCommandText = 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 却可以动态地添加。

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

本帖的最后,如前三篇帖子一样,照例提供一位 Java 大师 - 结城浩,所绘制的 Proxy Pattern 趣味四格漫画,原地址如下:

Giko 猫谈 DP 四格漫画:
http://www.javaworld.com.tw/jute/post/view?bid=44&id=7749&sty=3&age=0&tpg=1&ppg=1#7749
http://www.hyuki.com/dp/cat_Proxy.html

∧_∧  敲敲敲  ╱
(    )  ∧ ∧ < WCF 算是 Remote Proxy 的一个例子....。
(    )  (,,゚Д゚)  ╲____________
______ (つ_つ____
|   日∇ ╲|ThinkPad|╲
|      =========  ╲

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
____________

| 喔~、是 Proxy Pattern 吗?
╲ __ __________
  |╱
  ∧_∧       ╱
  ( ・∀・)  ∧ ∧ < 你你是谁? ...有..有什么事嬷你?・・・
  (  ⊃ )  (゚Д゚;)  ╲____________
________(つ_つ____
|    日∇ ╲|ThinkPad|╲
|       =========  ╲

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
__________

| 被情势所逼,才创建对象的一种 Pattern..恩
╲ __ ________
  |╱
  ∧_∧       ╱
  ( ・∀・)  ∧ ∧ < 恩..那算是 Virtual Proxy 的一种....
 (     )  (;゚Д゚)  ╲____________
_____ (つ_つ____
|     日∇╲|ThinkPad|╲
|       =========  ╲

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
__________

| 项目截止日快到了才开始 coding...恩
╲ __ ________
  |╱
 ∧_∧       ╱
 ( ・∀・)  ∧ ∧ < 我又没说!!
 (  ⊃ )  (゚Д゚;) ╲____________
_____(つ_つ____
|   日∇ ╲|ThinkPad|╲
|      =========  ╲
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

         /
  ∧ ∧ < 这可笑不出来
  (    ) ╲_____________
∼(___ノ 

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

艾伟_转载:C# Design Patterns (4) - Proxy相关推荐

  1. 艾伟_转载:C# Design Patterns (3) - Decorator

    Decorator Pattern (装饰模式) 装饰模式可「动态」地给一个对象添加一些额外的职责,提供有别于「继承」的另一种选择.就扩展功能而言,Decorator Pattern 透过 Aggre ...

  2. 艾伟_转载:探索.Net中的委托

    废话 我本来以为委托很简单,本来只想简简单单的说说委托背后的东西,委托的使用方法.原本只想解释一下那句:委托是面向对象的.类型安全的函数指针.可没想到最后惹出一堆的事情来,越惹越多,罪过,罪过.本文后 ...

  3. 艾伟_转载:学习 ASP.NET MVC (第五回)理论篇

    本系列文章导航 学习 ASP.NET MVC (第一回)理论篇 学习 ASP.NET MVC (第二回)实战篇 学习 ASP.NET MVC (第三回)实战篇 学习 ASP.NET MVC (第四回) ...

  4. 艾伟_转载:C#语言基础常见问题汇总

    概述 1.什么是C#? C#是Microsoft公司设计的一种编程语言.它松散地基于C/C++,并且有很多方面和Java类似. Microsoft是这样描述C#的:"C#是从C和C++派生来 ...

  5. 艾伟_转载:.NET 4.0中数组的新增功能

    1.两数组是否"相等"? 在实际开发中,有时我们需要比对两个数组是否拥有一致的元素,例如,以下两个数组由于拥有相同的元素,因此被认为是相等的: int[] arr1 = new i ...

  6. 艾伟_转载:从ASP.NET的PHP执行速度比较谈起

    上星期我在InfoQ发表了一篇新闻,对Joe Stagner在博客上发表的三篇关于ASP.NET与PHP性能对比的文章进行了总结.写新闻其实挺不爽的,因为不能夹杂个人的看法,只能平铺直叙陈述事实.当然 ...

  7. 艾伟_转载:.NET设计模式:观察者模式(Observer Pattern)

    概述 在软件构建过程中,我们需要为某些对象建立一种"通知依赖关系" --一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知.如果这样的依赖关系过于紧密,将 ...

  8. 艾伟_转载:.NET内存管理、垃圾回收

    1. Stack和Heap 每个线程对应一个stack,线程创建的时候CLR为其创建这个stack,stack主要作用是记录函数的执行情况.值类型变量(函数的参数.局部变量 等非成员变量)都分配在st ...

  9. 艾伟_转载:ASP.NET模板引擎技术

    以前听我朋友说起php的模板引擎技术的时候似懂非懂哪时感觉真的很强,一直在想asp.net有这种技术吗?我不知道我的理解是不是对的.其实 asp.net的模板引擎技术就是先建好一个静态的html页面我 ...

最新文章

  1. 【函数】02、函数进阶
  2. matlab画倾斜的椭球,在MATLAB中绘制椭圆和椭球
  3. Java删除文件(delete file in java)
  4. proto的介绍和基础使用
  5. 持续交付二:为什么需要多个环境
  6. 计算机科学技术的教育应用论文,浅谈计算机科学技术在计算机教学中的应用论文...
  7. window 快捷键使用 + idear 编辑器使用
  8. 入手5G手机别太急!国内部分5G手机可能有网没信号
  9. java 访问频率限制_配置URL的访问频率限制
  10. 少年,这有套《街霸2》AI速成心法,想传授于你……
  11. 如何读取二进制图片-.ashx一般处理程序
  12. SVN 小乌龟(TortoiseSVN)本地文件更新报错Another process is blocking the working copy database 解决方法
  13. 【测试能力提升】Jira 和禅道数据库分析,方便你写周报、写总结、出报告
  14. android 锁屏应用,推荐几款好用的安卓(Android)手机锁屏软件
  15. 华为云数据库GaussDB(for openGauss):初次见面,认识一下
  16. Android陀螺仪加速度传感器
  17. linux夏令时配置文件,Linux中有关时区及夏令时设置(TZ环境变量)
  18. 数据分析模型篇—波士顿矩阵
  19. vim 打开文件乱码
  20. 小马哥===教你修改内核boot.img来实现手机root权限

热门文章

  1. linux bootstrap,Bootstrap示例
  2. 手机php文件怎么改后辍,php修改文件后缀名的方法
  3. 智能车竞赛第十六届比赛参赛同学提问-环境参数-5月15
  4. 2021年春季学期-信号与系统-第四次作业参考答案-MATLAB实验题2
  5. 2021-春季学习-智能车技术创新与实践-Lesson2
  6. 二进制安装mysql5.6_轻松使用二进制安装Mysql5.6
  7. python3.7如何安装库_Python3.7版库的安装以及常用方法(十分简单)
  8. win10怎么设置开机启动项目_苹果mac开机启动项怎么设置
  9. mysql innodb 主键,Mysql InnoDB 引擎 主键性能
  10. Linux里gedit和vim哪个好,linux下有没有leafpad一样快,emacs/vim一样强大,gedit一样易用的图形界面文本编辑器?...