C# Design Patterns (4) - Proxy
本帖介绍 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 模式的经典类图
上方图 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 对象,以节省内存、控制创建成本昂贵的资源。
代理的主要目的之一,是把复杂性封装起来,让客户端程序在引用上更容易,而不需要顾虑藏在身后这些复杂的逻辑。如上方这个示例,我们可以把「载入图片、绘制图片在屏幕上」这些较复杂的 .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 的类图
--------------------------------------------------------
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|╲
| ========= ╲
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
/
∧ ∧ < 这可笑不出来
( ) ╲_____________
∼(___ノ
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -