设计模式的征途—13.代理(Proxy)模式
所谓代购,简单说来就是找人帮忙购买所需要的商品。代购分为两种类型,一种是因为在当地买不到某件商品,又或者是因为当地这件商品的价格比其他地区的贵,因此托人在其他地区甚至国外购买该商品,然后通过快递发货或直接携带回来。另一种则是消费者对想要购买的商品相关信息的缺乏,自己无法确定其实际价值,因此只好委托中介讲价或购买。在软件开发中,有一种设计模式可以提供与代购类似的功能,由于某些原因,客户端不想或者不能直接访问某个对象,此时可以通过一个称之为“代理”的第三者来实现间接访问,该方案对应的设计模式则被称为代理模式。
外观模式(Proxy) | 学习难度:★★★☆☆ | 使用频率:★★★★☆ |
一、收费商务查询系统的设计
M公司承接了某信息咨询公司的收费商务信息查询系统的开发任务,该系统的基本需求如下:
(1)在进行商务信息查询之前用户需要通过身份验证,只有合法用户才能够使用该查询系统。
(2)在进行商务信息查询时,系统需要记录查询日志,以便根据查询次数收取查询费用。
M公司开发人员已经完成了商务信息查询模块的开发任务,他们希望能够以一种松耦合的方式向原有系统增加身份验证和日志记录功能,客户端代码可以无区别地对待原始的商务信息查询模块和增加新功能之后的商务信息查询模块,而且可能在将来还要在该信息查询模块中增加一些新的功能。
M公司开发人员通过分析,决定采用一种间接访问的方式来实现该商务信息查询系统的设计,在客户端对象和信息查询对象之间增加一个代理对象,让代理对象来实现验证和日志记录功能,而无须直接对原有的商务信息查询对象进行修改,如下图所示:
这种设计方案即为代理模式,它为对象的访问提供了一种设计方案,而且具有多种不同的类型,应用相当广泛。
二、代理模式概述
2.1 代理模式简介
代理(Proxy)模式:给某一个对象提供一个代理,并由代理对象控制对原对象的引用。代理模式是一种对象结构型模式。
可以看重,代理模式的重点就在于引入了一个新的代理对象,代理对象可以在客户端对象和目标对象之间起到中介的作用,去掉客户不能看到的内容和服务或者添加客户需要的额外服务。
2.2 代理模式结构
代理模式主要包含以下3个角色:
(1)Subject(抽象主题角色):声明真实主题和代理主题的共同接口,使得在任何使用真实主题的地方都可以使用代理主题。
(2)Proxy(代理主题角色):代理主题角色内部包含了对真实主题的引用,从而可以在任何时候操作真实主题对象;
(3)RealSubject(真实主题角色):定义了代理角色所代表的真实对象,在真实主题角色中实现了真实的业务操作。
三、实现收费商务查询系统
3.1 系统设计结构
3.2 具体代码实现
(1)抽象主题 => ISearcher接口
/// <summary> /// 抽象主题类:抽象查询接口 /// </summary> public interface ISearcher { string DoSearch(string userID, string keyword); }
(2)真实主题 => RealSearcher类
/// <summary> /// 真是主题类:具体查询器 /// </summary> public class RealSearcher { /// <summary> /// 模拟查询商务信息 /// </summary> /// <returns></returns> public string DoSearch(string userID, string keyword) { Console.WriteLine("{0} 使用关键词 {1}", userID, keyword); return "返回具体内容"; } }
此外,还有两个业务类:AccessValidator用于验证用户身份,Logger则用于记录日志。
/// <summary> /// 业务类:身份验证类 /// </summary> public class AccessValidator { /// <summary> /// 模拟实现登录验证 /// </summary> /// <param name="userID"></param> /// <returns></returns> public bool Validate(string userID) { Console.WriteLine("在数据库中验证用户 {0} 是否是合法用户?", userID); if (userID.Equals("杨过", StringComparison.OrdinalIgnoreCase)) { Console.WriteLine("{0} 登录成功!", userID); return true; } else { Console.WriteLine("{0} 登录失败!", userID); return false; } } } /// <summary> /// 业务类:日志记录类 /// </summary> public class Logger { /// <summary> /// 模拟实现日志记录 /// </summary> /// <param name="userID"></param> public void Log(string userID) { Console.WriteLine("更新数据库,用户 {0} 查询次数加1!", userID); } }
(3)代理主题 => ProxySearcher类
/// <summary> /// 代理主题类:代理查询 /// </summary> public class ProxySearcher : ISearcher { private RealSearcher searcher = new RealSearcher(); // 维持一个对真实主题的引用 private AccessValidator validator; private Logger logger; public string DoSearch(string userID, string keyword) { if (Validate(userID)) { string result = searcher.DoSearch(userID, keyword); this.Log(userID); return result; } return null; } /// <summary> /// 创建访问验证对象并调用其Validate()方法进行身份验证 /// </summary> /// <returns></returns> public bool Validate(string userID) { validator = new AccessValidator(); return validator.Validate(userID); } /// <summary> /// 创建日志记录器并调用Log()方法实现日志记录 /// </summary> /// <param name="userID"></param> public void Log(string userID) { logger = new Logger(); logger.Log(userID); } }
(4)客户端调用
① 为了提高系统可扩展性,这里将代理主题类存在了配置文件中
<?xml version="1.0" encoding="utf-8" ?> <configuration> <appSettings> <!-- Proxy Setting --> <add key="ProxyName" value="Manulife.ChengDu.DesignPattern.Proxy.ProxySearcher, Manulife.ChengDu.DesignPattern.Proxy" /> </appSettings> </configuration>
② 客户端调试代码
public class Program { public static void Main(string[] args) { ISearcher searcher = AppConfigHelper.GetProxyInstance() as ISearcher; if (searcher != null) { string result = searcher.DoSearch("杨过", "玉女心经"); } Console.ReadKey(); } }
这里AppConfigHelper主要用于访问配置文件并通过反射生成实例对象
public class AppConfigHelper { public static string GetProxyName() { string factoryName = null; try { factoryName = System.Configuration.ConfigurationManager.AppSettings["ProxyName"]; } catch (Exception ex) { Console.WriteLine(ex.Message); } return factoryName; } public static object GetProxyInstance() { string assemblyName = AppConfigHelper.GetProxyName(); Type type = Type.GetType(assemblyName); var instance = Activator.CreateInstance(type); return instance; } }
③ 运行结果
四、代理模式总结
4.1 主要优点
(1)协调了调用者和被调用者,一定程度上降低了系统的耦合度 => 符合迪米特法则
(2)客户端针对抽象主题角色编程,增加和更换代理类无须修改源代码 => 符合开闭原则
4.2 应用场景
(1)客户端需要访问远程主机中的对象时 => 远程代理
(2)需要一个消耗资源较少的对象来代表一个消耗资源较多的对象 => 降低系统开销
(3)需要控制对一个对象的访问,为不同用户提供不同级别的访问权限 => 保护代理
参考资料
刘伟,《设计模式的艺术—软件开发人员内功修炼之道》