概述:
在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。
意图:
给某一个对象提供一个代理或占位符,并由代理对象来控制对原对象的访问。
实质:
代理模式是常用的结构型设计模式之一,当无法直接访问某个对象或访问某个对象存在困难时可以通过一个代理对象来间接访问,为了保证客户端使用的透明性,所访问的真实对象与代理对象需要实现相同的接口。根据代理模式的使用目的不同,代理模式又可以分为多种类型,例如保护代理、远程代理、虚拟代理、缓冲代理等,它们应用于不同的场合,满足用户的不同需求。
使用场景:
- 当客户端对象需要访问远程主机中的对象时可以使用远程代理。
- 当需要用一个消耗资源较少的对象来代表一个消耗资源较多的对象,从而降低系统开销、缩短运行时间时可以使用虚拟代理,例如一个对象需要很长时间才能完成加载时。
- 当需要为某一个被频繁访问的操作结果提供一个临时存储空间,以供多个客户端共享访问这些结果时可以使用缓冲代理。通过使用缓冲代理,系统无须在客户端每一次访问时都重新执行操作,只需直接从临时缓冲区获取操作结果即可。
- 当需要控制对一个对象的访问,为不同用户提供不同级别的访问权限时可以使用保护代理。
- 当需要为一个对象的访问(引用)提供一些额外的操作时可以使用智能引用代理。
参与者:
- Subject(抽象主题角色):它声明了真实主题和代理主题的共同接口,这样一来在任何使用真实主题的地方都可以使用代理主题,客户端通常需要针对抽象主题角色进行编程。
- Proxy(代理主题角色):它包含了对真实主题的引用,从而可以在任何时候操作真实主题对象;在代理主题角色中提供一个与真实主题角色相同的接口,以便在任何时候都可以替代真实主题;代理主题角色还可以控制对真实主题的使用,负责在需要的时候创建和删除真实主题对象,并对真实主题对象的使用加以约束。通常,在代理主题角色中,客户端在调用所引用的真实主题操作之前或之后还需要执行其他操作,而不仅仅是单纯调用真实主题对象中的操作。
- RealSubject(真实主题角色):它定义了代理角色所代表的真实对象,在真实主题角色中实现了真实的业务操作,客户端可以通过代理主题角色间接调用真实主题角色中定义的操作。
UML图:
多种实现方式:


public abstract class Subject { public abstract void Request(); } class RealSubject : Subject { public override void Request() { //真正请求 } } class Proxy : Subject { RealSubject realSubject; public override void Request() { if (realSubject == null) realSubject = new RealSubject(); realSubject.Request(); } } public class Client { static void Main(string[] args) { Proxy proxy = new Proxy(); proxy.Request(); } }
1)远程代理(Remote Proxy):为一个位于不同的地址空间的对象提供一个本地的代理对象,这个不同的地址空间可以是在同一台主机中,也可是在另一台主机中。使得客户端程序可以访问在远程主机上的对象,远程主机可能具有更好的计算性能与处理速度,可以快速响应并处理客户端的请求。远程代理可以将网络的细节隐藏起来,使得客户端不必考虑网络的存在。客户端完全可以认为被代理的远程业务对象是在本地而不是在远程,而远程代理对象承担了大部分的网络通信工作,并负责对远程业务方法的调用。


public interface IContract { void Action(); } //继承MarshalByRefObject为了传引用封送,默认不继承是传值封送(加[Serializable]) public class ContractServer : MarshalByRefObject, IContract { public void Action() { // Real Action } } public class Server { #region 注册通道 private static void RegisterChannel() { IChannelReceiver tcpChannel = new TcpChannel(8888); ChannelServices.RegisterChannel(tcpChannel, false); IChannelReceiver httpChannel = new HttpChannel(8889); ChannelServices.RegisterChannel(httpChannel, false); } // 自定义通道 private static void CustomRegisterChannel() { IServerChannelSinkProvider formatter = new BinaryServerFormatterSinkProvider(); IDictionary propertyDic = new Hashtable(); propertyDic["name"] = "CustomTcp"; propertyDic["port"] = 8886; IChannel tcpChannel = new TcpChannel(propertyDic, null, formatter); ChannelServices.RegisterChannel(tcpChannel, false); formatter = new SoapServerFormatterSinkProvider(); propertyDic = new Hashtable(); propertyDic["name"] = "CustomHttp"; propertyDic["port"] = 8887; IChannel httpChannel = new HttpChannel(propertyDic, null, formatter); ChannelServices.RegisterChannel(httpChannel, false); } #endregion #region 注册对象 允许哪些类型可以被远程程序访问 //客户端激活 每一次new都会调用构造函数 private static void ClientActivated() { Type type = typeof(IContract); RemotingConfiguration.RegisterActivatedServiceType(type); } //注册服务激活对象 SingleCall new都不会调用构造函数 第一次调用方法会调用2次构造函数 private static void ServerActivatedSingleCall() { Type type = typeof(IContract); RemotingConfiguration.RegisterWellKnownServiceType(type, "ServerActivated", WellKnownObjectMode.SingleCall); } //注册服务激活对象 Singleton new不会调用构造函数 而且只调用构造函数一次 private static void ServerActivatedSingleton() { Type type = typeof(IContract); RemotingConfiguration.RegisterWellKnownServiceType(type, "ServerActivated", WellKnownObjectMode.Singleton); } #endregion public static void Main(string[] args) { //注册通道 RegisterChannel(); //CustomRegisterChannel(); //注册对象 //ClientActivated(); //ServerActivatedSingleCall(); ServerActivatedSingleton(); RemotingConfiguration.ApplicationName = "RemoteHost"; } } public class ContractProxy : IContract { private ContractServer realContract; public void Action() { realContract.Action(); } public static ContractProxy() { #region 注册远程对象 有两种方式:客户激活对象,服务激活对象 ClientActivated(); //ServerActivated(); #endregion } public ContractProxy() { //创建远程对象:已经注册对象,可以直接使用new操作符创建对象、如果不进行注册来创建远程对象,可以通过 RemotingServices.Connect()、Activator.GetObject()、Activator.CreateInstance()方法 realContract = new ContractServer(); //使用new创建 string url = "tcp://127.0.0.1:8888/RemoteHost/ServerActivated"; // 方式1 realContract = (ContractServer)RemotingServices.Connect(typeof(ContractServer), url); // 方式2 realContract = (ContractServer)Activator.GetObject(typeof(ContractServer), url); //SingleCall模式是由GetObject()来激活 // 方式3 object[] activationAtt = { new UrlAttribute(url) }; realContract = (ContractServer)Activator.CreateInstance(typeof(ContractServer), null, activationAtt); //客户端激活模式,则通过CreateInstance()来激活 } #region 注册远程对象 // 客户激活对象 private static void ClientActivated() { Type type = typeof(ContractServer); string url = @"tcp://127.0.0.1:8888/RemoteHost"; //url = @"http://127.0.0.1:8889/RemoteHost"; RemotingConfiguration.RegisterActivatedClientType(type, url); } // 服务激活对象 private static void ServerActivated() { Type type = typeof(ContractServer); string url = "tcp://127.0.0.1:8888/RemoteHost/ServerActivated"; //url = @"http://127.0.0.1:8889/RemoteHost/ServerActivated"; RemotingConfiguration.RegisterWellKnownClientType(type, url); } #endregion } public class Client { static void Main(string[] args) { ContractProxy proxy = new ContractProxy(); proxy.Action(); } }
客户端对象不能直接访问远程主机中的业务对象,只能采取间接访问的方式。远程业务对象在本地主机中有一个代理对象,该代理对象负责对远程业务对象的访问和网络通信,它对于客户端对象而言是透明的。客户端无须关心实现具体业务的是谁,只需要按照服务接口所定义的方式直接与本地主机中的代理对象交互即可。
特点:为两个不同地址空间对象的访问提供了一种实现机制,可以将一些消耗资源较多的对象和操作移至性能更好的计算机上,提高系统的整体运行效率。
2)虚拟代理(Virtual Proxy):如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。


public class ImageProxy { private static Image realImage = null; private bool bFirst = false; public Image GetRealImage() { if (realImage != null) return realImage; else if (!bFirst) { bFirst = true; Thread retrieveThread = new Thread(new ThreadStart(DownloadImage)); retrieveThread.Start(); } return LocalImage(); } private void DownloadImage() { string url = @"http://www.google.com.hk/logos/2011/fathersday11-hp.jpg"; HttpWebRequest request = HttpWebRequest.Create(url) as HttpWebRequest; HttpWebResponse response = request.GetResponse() as HttpWebResponse; realImage = Image.FromStream(response.GetResponseStream()); } private Image LocalImage() { return new Bitmap(System.Reflection.Assembly.GetExecutingAssembly(). GetManifestResourceStream(this.GetType().Namespace.ToString() + ".local.jpg")); } }
特点:通过一个消耗资源较少的对象来代表一个消耗资源较多的对象,可以在一定程度上节省系统的运行开销。
Copy-on-Write代理:虚拟代理的一种。把复制(克隆)拖延到只有在客户端需要时,才真正采取行动。
String 类型中引入了 Copy-On-Write(写时拷贝) 技术,写时拷贝:在复制一个对象时并不是真的在内存中把原来对象的数据复制一份到另外一个地址,而是在新对象的内存映射表中指向同原对象相同的位置,并且把那块内存的 Copy-On-Write 位设为 1。在对这个对象执行读操作的时候,内存数据没有变动,直接执行就可以。在写的时候,才真正将原始对象复制一份到新的地址,修改新对象的内存映射表到这个新的位置,然后往这里写。有一定经验的程序员应该都知道,Copy-On-Write(写时拷贝) 技术使用了 "引用计数" 方式,会有一个变量用于保存引用的数量。当第一个类构造时,String 的构造函数会根据传入的参数从堆上分配内存,当有其它类需要这块内存时,这个计数为自动累加。当有类析构时,这个计数会减一,直到最后一个类析构时,此时的引用计数为 1 或是 0,此时,程序才会真正的释放这块从堆上分配的内存。
3)保护代理(Protect Proxy):控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。
4)智能引用代理(Smart Reference Proxy):当一个对象被引用时,提供一些额外的操作,例如将对象被调用的次数记录下来等。


class Proxy : Subject { RealSubject realSubject = new RealSubject(); //维持一个对真实主题的引用 public override void Request() { if(true) //针对不同的权限进行操作 { realSubject.Request(); ExtraOperating(); } } private void ExtraOperating() { //进行额外的操作,实现智能引用 } } public class Client { static void Main(string[] args) { //读取配置文件 string proxyStr = ConfigurationManager.AppSettings["proxy"]; //反射生成对象,针对抽象编程,客户端无须分辨真实主题类和代理类 Proxy proxy = (Proxy)Assembly.Load("Proxy").CreateInstance(proxyStr); proxy.Request(); }
特点:控制对一个对象的访问权限,为不同用户提供不同级别的使用权限。
5)缓冲代理(Cache Proxy):为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。
如果需要访问的数据在缓存中已经存在,则无须再重复执行获取数据的方法,直接返回存储在缓存中的数据即可。由于原有业务对象(真实对象)和新增代理对象暴露在外的方法是一致的,因而对于调用方即客户端而言,调用代理对象与真实对象并没有实质的区别。增加方法缓存机制,必须引入一个新的对象去控制(也就是包装了一下)原来的BLL业务逻辑对象,这些新的对象对应于代理模式中的缓存代理对象。


public static class ProductDataProxy { private static readonly bool enableCaching = bool.Parse(ConfigurationManager. AppSettings["EnableCaching"]); public static IList GetProductsByCategory(string category) { Product product = new Product(); //如果缓存被禁用,则直接通过product对象来获取数据 if (!enableCaching) { return product.GetProductsByCategory(category); } string key = "product_by_category_" + category; //从缓存中获取数据 IList data = (IList )HttpRuntime.Cache[key]; //如果缓存中没有数据则执行如下代码 if (data == null) { data = product.GetProductsByCategory(category); //通过工厂创建AggregateCacheDependency对象 AggregateCacheDependency cd = DependencyFacade.GetProductDependency (); //将数据存储在缓存中,并添加必要的AggregateCacheDependency对象 HttpRuntime.Cache.Add(key, data, cd, DateTime.Now.AddHours(product Timeout), Cache.NoSlidingExpiration, CacheItemPriority.High, null); } return data; } }
特点:为某一个操作的结果提供临时的缓存存储空间,以便在后续使用中能够共享这些结果,优化系统性能,缩短执行时间。
代理模式(Proxy)VS 装饰者(Decorator)
意图:它们都提供间接访问对象层,都保存被调用对象的引用。
代理模式(Proxy):为另一个对象提供一个替身或占位符以控制对这个对象的访问,简而言之就是用一个对象来代表另一个对象。
装饰者(Decorator):动态地给一个对象添加一些额外的职责,就增加功能来说,Decorator模式比生成子类更为灵活,它避免了类爆炸问题,像装饰者(Decorator),代理模式(Proxy)组成一个对象并提供相同的接口,但代理模式并不关心对象动态职能的增减。
在代理模式(Proxy)中Subject定义了主要的功能,而且Proxy根据Subject提供功能控制对象的访问权限。在装饰者(Decorator)中Component只是提供了其中的一些功能,需要通过装饰链动态给对象增加职能。
总结:以上纯属个人的理解,对于有些地方觉得还是理解不是很深,有不足之处和错误的地方希望大家帮我指出。谢谢