设计模式之代理模式(二)

15.3 代理模式应用实例

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

  1. 实例说明

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

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

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

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

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

  1. 实例分析及类图

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

设计模式之代理模式(二) - 图1

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

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

设计模式之代理模式(二) - 图2

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

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

  1. 实例代码

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

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

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

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

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

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

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

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

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

  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. public string DoSearch(string userId, string keyword)
  10. {
  11. //如果身份验证成功,则执行查询
  12. if (this.Validate(userId))
  13. {
  14. string result = searcher.DoSearch(userId, keyword); //调用真实主题对象的查询方法
  15. this.Log(userId); //记录查询日志
  16. return result; //返回查询结果
  17. }
  18. else
  19. {
  20. return null;
  21. }
  22. }
  23. //创建访问验证对象并调用其Validate()方法实现身份验证
  24. public bool Validate(string userId)
  25. {
  26. validator = new AccessValidator();
  27. return validator.Validate(userId);
  28. }
  29. //创建日志记录对象并调用其Log()方法实现日志记录
  30. public void Log(string userId)
  31. {
  32. logger = new Logger();
  33. logger.Log(userId);
  34. }
  35. }
  36. }

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

  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:客户端测试类

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

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

  1. 在数据库中验证用户'杨过'是否是合法用户?
  2. '杨过'登录成功!
  3. 用户'杨过'使用关键词'玉女心经'查询商务信息!
  4. 更新数据库,用户'杨过'查询次数加1

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