设计模式之代理模式

代理模式是常用的结构型设计模式之一,当无法直接访问某个对象或访问某个对象存在困难时可以通过一个代理对象来间接访问,为了保证客户端使用的透明性,所访问的真实对象与代理对象需要实现相同的接口。根据代理模式的使用目的不同,代理模式又可以分为多种类型,例如保护代理、远程代理、虚拟代理、缓冲代理等,它们应用于不同的场合,满足用户的不同需求。

 

15.1 代理模式概述

       近年来,代购已逐步成为电子商务的一个重要分支。何谓代购,简单来说就是找人帮忙购买所需要的商品,当然你可能需要向实施代购的人支付一定的费用。代购通常分为两种类型:一种是因为在当地买不到某件商品,又或者是因为当地这件商品的价格比其他地区的贵,因此托人在其他地区甚至国外购买该商品,然后通过快递发货或者直接携带回来;还有一种代购,由于消费者对想要购买的商品相关信息的缺乏,自已无法确定其实际价值而又不想被商家宰,只好委托中介机构帮其讲价或为其代买。代购网站为此应运而生,它为消费者提供在线的代购服务,如果看中某国外购物网站上的商品,可以登录代购网站填写代购单并付款,代购网站会帮助进行购买然后通过快递公司将商品发送给消费者。商品代购过程如图15-1所示:

设计模式之代理模式_第1张图片

15-1 商品代购示意图

       在软件开发中,也有一种设计模式可以提供与代购网站类似的功能。由于某些原因,客户端不想或不能直接访问一个对象,此时可以通过一个称之为“代理”的第三者来实现间接访问,该方案对应的设计模式被称为代理模式。

       代理模式是一种应用很广泛的结构型设计模式,而且变化形式非常多,常见的代理形式包括远程代理、保护代理、虚拟代理、缓冲代理、智能引用代理等,后面将学习这些不同的代理形式。

       代理模式定义如下:

代理模式:给某一个对象提供一个代理或占位符,并由代理对象来控制对原对象的访问。

Proxy Pattern: Provide a surrogate or placeholder for another object to control access to it.

       代理模式是一种对象结构型模式。在代理模式中引入了一个新的代理对象,代理对象在客户端对象和目标对象之间起到中介的作用,它去掉客户不能看到的内容和服务或者增添客户需要的额外的新服务。

 

15.2 代理模式结构与实现

15.2.1 模式结构

      代理模式的结构比较简单,其核心是代理类,为了让客户端能够一致性地对待真实对象和代理对象,在代理模式中引入了抽象层,代理模式结构如图15-2所示:

设计模式之代理模式_第2张图片

15-2 代理模式结构图

       由图15-2可知,代理模式包含如下三个角色:

       (1) Subject(抽象主题角色):它声明了真实主题和代理主题的共同接口,这样一来在任何使用真实主题的地方都可以使用代理主题,客户端通常需要针对抽象主题角色进行编程。

       (2) Proxy(代理主题角色):它包含了对真实主题的引用,从而可以在任何时候操作真实主题对象;在代理主题角色中提供一个与真实主题角色相同的接口,以便在任何时候都可以替代真实主题;代理主题角色还可以控制对真实主题的使用,负责在需要的时候创建和删除真实主题对象,并对真实主题对象的使用加以约束。通常,在代理主题角色中,客户端在调用所引用的真实主题操作之前或之后还需要执行其他操作,而不仅仅是单纯调用真实主题对象中的操作。

       (3) RealSubject(真实主题角色):它定义了代理角色所代表的真实对象,在真实主题角色中实现了真实的业务操作,客户端可以通过代理主题角色间接调用真实主题角色中定义的操作。

 

15.2.2 模式实现

       代理模式的结构图比较简单,但是在真实的使用和实现过程中要复杂很多,特别是代理类的设计和实现。

       抽象主题类声明了真实主题类和代理类的公共方法,它可以是接口、抽象类或具体类,客户端针对抽象主题类编程,一致性地对待真实主题和代理主题,典型的抽象主题类代码如下:

[csharp]  view plain copy
  1. abstract class Subject  
  2. {  
  3.     public abstract void Request();  
  4. }  

       真实主题类继承了抽象主题类,提供了业务方法的具体实现,其典型代码如下:

[csharp]  view plain copy
  1. class RealSubject : Subject  
  2. {  
  3.     public override void Request()  
  4.     {  
  5.         //业务方法具体实现代码  
  6.     }  
  7. }  

       代理类也是抽象主题类的子类,它维持一个对真实主题对象的引用,调用在真实主题中实现的业务方法,在调用时可以在原有业务方法的基础上附加一些新的方法来对功能进行扩充或约束,最简单的代理类实现代码如下:

[csharp]  view plain copy
  1. class Proxy : Subject  
  2. {  
  3.     private RealSubject realSubject = new RealSubject(); //维持一个对真实主题对象的引用  
  4.   
  5.     public void PreRequest()   
  6.     {  
  7.         …...  
  8.     }  
  9.   
  10.     public override void Request()   
  11.     {  
  12.         PreRequest();  
  13.         realSubject.Request(); //调用真实主题对象的方法  
  14.          PostRequest();  
  15.     }  
  16.   
  17.     public void PostRequest()   
  18.     {  
  19.         ……  
  20.     }  
  21. }  

       在实际开发过程中,代理类的实现比上述代码要复杂很多,代理模式根据其目的和实现方式不同可分为很多种类,其中常用的几种代理模式简要说明如下:

       (1) 远程代理(Remote Proxy)为一个位于不同的地址空间的对象提供一个本地的代理对象,这个不同的地址空间可以是在同一台主机中,也可是在另一台主机中,远程代理又称为大使(Ambassador)

       (2) 虚拟代理(Virtual Proxy)如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。

       (3) 保护代理(Protect Proxy)控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。

       (4) 缓冲代理(Cache Proxy)为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。

       (5) 智能引用代理(Smart Reference Proxy)当一个对象被引用时,提供一些额外的操作,例如将对象被调用的次数记录下来等。

       在这些常用的代理模式中,有些代理类的设计非常复杂,例如远程代理类,它封装了底层网络通信和对远程对象的调用,其实现较为复杂。

 代理模式应用实例

       下面通过一个应用实例来进一步学习和理解代理模式。

       1. 实例说明

       某软件公司承接了某信息咨询公司的收费商务信息查询系统的开发任务,该系统的基本需求如下:

       (1) 在进行商务信息查询之前用户需要通过身份验证,只有合法用户才能够使用该查询系统;

       (2) 在进行商务信息查询时系统需要记录查询日志,以便根据查询次数收取查询费用。

       该软件公司开发人员已完成了商务信息查询模块的开发任务,现希望能够以一种松耦合的方式向原有系统增加身份验证和日志记录功能,客户端代码可以无区别地对待原始的商务信息查询模块和增加新功能之后的商务信息查询模块,而且可能在将来还要在该信息查询模块中增加一些新的功能。

       试使用代理模式设计并实现该收费商务信息查询系统。

       2. 实例分析及类图

       通过分析,可以采用一种间接访问的方式来实现该商务信息查询系统的设计,在客户端对象和信息查询对象之间增加一个代理对象,让代理对象来实现身份验证和日志记录等功能,而无须直接对原有的商务信息查询对象进行修改,如图15-3所示:

设计模式之代理模式_第3张图片

15-3 商务信息查询系统设计方案示意图

       在图15-3中,客户端对象通过代理对象间接访问具有商务信息查询功能的真实对象,在代理对象中除了调用真实对象的商务信息查询功能外,还增加了身份验证和日志记录等功能。使用代理模式设计该商务信息查询系统,结构图如图15-4所示。

设计模式之代理模式_第4张图片

15-4 商务信息查询系统结构图

       在图15-4中,业务类AccessValidator用于验证用户身份,业务类Logger用于记录用户查询日志,Searcher充当抽象主题角色,RealSearcher充当真实主题角色,ProxySearcher充当代理主题角色。

       3. 实例代码

       (1) AccessValidator:身份验证类,业务类,它提供方法Validate()来实现身份验证。

[csharp]  view plain copy
  1. //AccessValidator.cs  
  2. using System;  
  3.   
  4. namespace ProxySample  
  5. {  
  6.     class AccessValidator  
  7.     {  
  8.         //模拟实现登录验证  
  9.         public bool Validate(string userId)   
  10.         {  
  11.             Console.WriteLine("在数据库中验证用户'" + userId + "'是否是合法用户?");  
  12.             if (userId.Equals("杨过")) {  
  13.                 Console.WriteLine("'{0}'登录成功!",userId);  
  14.                 return true;  
  15.             }  
  16.             else {  
  17.                 Console.WriteLine("'{0}'登录失败!", userId);  
  18.                 return false;  
  19.             }  
  20.         }  
  21.     }  
  22. }  

       (2) Logger:日志记录类,业务类,它提供方法Log()来保存日志。

[csharp]  view plain copy
  1. //Logger.cs  
  2. using System;  
  3.   
  4. namespace ProxySample  
  5. {  
  6.     class Logger  
  7.     {  
  8.         //模拟实现日志记录  
  9.         public void Log(string userId) {  
  10.             Console.WriteLine("更新数据库,用户'{0}'查询次数加1!",userId);  
  11.         }  
  12.     }  
  13. }  

       (3) Searcher:抽象查询类,充当抽象主题角色,它声明了DoSearch()方法。

[csharp]  view plain copy
  1. //Searcher.cs  
  2. namespace ProxySample  
  3. {  
  4.     interface Searcher  
  5.     {  
  6.         string DoSearch(string userId, string keyword);  
  7.     }  
  8. }  

       (4) RealSearcher:具体查询类,充当真实主题角色,它实现查询功能,提供方法DoSearch()来查询信息。

[csharp]  view plain copy
  1. //RealSearcher.cs  
  2. using System;  
  3.   
  4. namespace ProxySample  
  5. {  
  6.     class RealSearcher : Searcher  
  7.     {  
  8.         //模拟查询商务信息  
  9.         public string DoSearch(string userId, string keyword) {  
  10.             Console.WriteLine("用户'{0}'使用关键词'{1}'查询商务信息!",userId,keyword);  
  11.             return "返回具体内容";  
  12.         }  
  13.     }  
  14. }  

       (5) ProxySearcher:代理查询类,充当代理主题角色,它是查询代理,维持了对RealSearcher对象、AccessValidator对象和Logger对象的引用。

[csharp]  view plain copy
  1. //ProxySearcher.cs  
  2. namespace ProxySample  
  3. {  
  4.     class ProxySearcher : Searcher  
  5.     {  
  6.         private RealSearcher searcher = new RealSearcher(); //维持一个对真实主题的引用  
  7.         private AccessValidator validator;  
  8.         private Logger logger;  
  9.   
  10.         public string DoSearch(string userId, string keyword)  
  11.         {  
  12.             //如果身份验证成功,则执行查询  
  13.             if (this.Validate(userId))  
  14.             {  
  15.                 string result = searcher.DoSearch(userId, keyword); //调用真实主题对象的查询方法  
  16.                 this.Log(userId); //记录查询日志  
  17.                 return result; //返回查询结果  
  18.             }  
  19.             else  
  20.             {  
  21.                 return null;  
  22.             }  
  23.         }  
  24.   
  25.         //创建访问验证对象并调用其Validate()方法实现身份验证  
  26.         public bool Validate(string userId)  
  27.         {  
  28.             validator = new AccessValidator();  
  29.             return validator.Validate(userId);  
  30.         }  
  31.   
  32.         //创建日志记录对象并调用其Log()方法实现日志记录  
  33.         public void Log(string userId)  
  34.         {  
  35.             logger = new Logger();  
  36.             logger.Log(userId);  
  37.         }  
  38.     }  
  39. }  

       (6) 配置文件App.config,在配置文件中存储了代理主题类类名。

[csharp]  view plain copy
  1. <?xml version="1.0" encoding="utf-8" ?>  
  2. <configuration>  
  3.   <appSettings>  
  4.     <add key="proxy" value="ProxySample.ProxySearcher"/>  
  5.   </appSettings>  
  6. </configuration>  

       (7) Program:客户端测试类

[csharp]  view plain copy
  1. //Program.cs  
  2. using System;  
  3. using System.Configuration;  
  4. using System.Reflection;  
  5.   
  6. namespace ProxySample  
  7. {  
  8.     class Program  
  9.     {  
  10.         static void Main(string[] args)  
  11.         {  
  12.             //读取配置文件  
  13.             string proxy = ConfigurationManager.AppSettings["proxy"];  
  14.   
  15.             //反射生成对象,针对抽象编程,客户端无须分辨真实主题类和代理类  
  16.             Searcher searcher;  
  17.             searcher = (Searcher)Assembly.Load("ProxySample").CreateInstance(proxy);  
  18.   
  19.             String result = searcher.DoSearch("杨过""玉女心经");  
  20.             Console.Read();  
  21.         }  
  22.     }  
  23. }  

       4. 结果及分析

        编译并运行程序,输出结果如下:

在数据库中验证用户'杨过'是否是合法用户?

'杨过'登录成功!

用户'杨过'使用关键词'玉女心经'查询商务信息!

更新数据库,用户'杨过'查询次数加1

       本实例是保护代理智能引用代理的应用实例,在代理类ProxySearcher中实现对真实主题类的权限控制和引用计数,如果需要在访问真实主题时增加新的访问控制机制和新功能,只需增加一个新的代理类,再修改配置文件,在客户端代码中使用新增代理类即可,源代码无须修改,符合开闭原则。


【作者:刘伟(Sunny) http://blog.csdn.net/lovelion】

你可能感兴趣的:(设计模式)