所谓代购,简单说来就是找人帮忙购买所需要的商品。代购分为两种类型,一种是因为在当地买不到某件商品,又或者是因为当地这件商品的价格比其他地区的贵,因此托人在其他地区甚至国外购买该商品,然后通过快递发货或直接携带回来。另一种则是消费者对想要购买的商品相关信息的缺乏,自己无法确定其实际价值,因此只好委托中介讲价或购买。在软件开发中,有一种设计模式可以提供与代购类似的功能,由于某些原因,客户端不想或者不能直接访问某个对象,此时可以通过一个称之为“代理”的第三者来实现间接访问,该方案对应的设计模式则被称为代理模式。

代理模式(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)需要控制对一个对象的访问,为不同用户提供不同级别的访问权限 => 保护代理

参考资料

  

  刘伟,《设计模式的艺术—软件开发人员内功修炼之道》

作者:周旭龙

出处:http://edisonchou.cnblogs.com

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。

设计模式的征途—13.代理(Proxy)模式的更多相关文章

  1. 设计模式C++描述----13.代理(Proxy)模式

    一. 举例说明 我们有时打开一个网站时会发现有这样的现象,网站上的文字都显示出来了,但是上面的图片还没显示,要等一会才能显示. 这些未打开的图片的位置上,还是会有图片框和一些等待的信息的,这就是代理模 ...

  2. Head First 设计模式 —— 13. 代理 (Proxy) 模式

    思考题 如何设计一个支持远程方法调用的系统?你要怎样才能让开发人员不用写太多代码?让远程调用看起来像本地调用一样,毫无瑕疵? P435 已经接触过 RPC 了,所以就很容易知道具体流程:客户端调用目标 ...

  3. 《图解设计模式》读书笔记9-2 Proxy模式

    目录 Proxy模式 示例程序 程序描述 类图 程序 角色和类图 角色 模式类图 思路拓展 提升速度 代理与委托 Http代理 与其他模式的关联 Decorator模式 Proxy模式 Proxy是代 ...

  4. 代理(Proxy)模式简介

    Proxy 模式简介 代理模式的两个应用: 打开文档时加载大图片 例如:如果有个对象是一张很大的图片,而这张图片需要花费很长时间才能显示出来,那么当这个图片包含在文档中的后面时,使用编辑器或浏览器打开 ...

  5. 设计模式之(三)Proxy模式

    今天学习Proxy模式.代理模式是在对已有对象操作困难或者不太方便时,选择用代理的方式对对象进行访问.Proxy实现的方法必须和被代理对象一致. 举一个简单的例子, 有一个Math类实现了IMath接 ...

  6. 设计模式(二十一)Proxy模式

    在面向对象编程中,“本人”和“代理人”都是对象.如果“本人”对象太忙了,有些工作无法自己亲自完成,就将其交给“代理人”对象负责. 示例程序的类图. 示例程序的时序图.从这个时序图可以看出,直到调用pr ...

  7. ⑦ 设计模式的艺术-13.代理(Proxy)模式

    为什么需要代理模式 中介隔离作用:在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口. 开闭原则,增加 ...

  8. 设计模式--代理(Proxy)模式

    在公司,经常性听到采购部的人说采购某样东材料,采购不了,需要通过代理商才可以.以前Insus.NET也做有一个练习<找人办事,代理设计模式(Proxy)>http://www.cnblog ...

  9. 十、设计模式之代理(Proxy)模式

    什么是代理模式 代理模式是对象的结构模式,为其他对象提供一种对象以控制对这个对象的访问. 代理模式的结构图如下:(源自大话设计模式)   Subject:定义了RealSubject和Proxy的公共 ...

随机推荐

  1. 关于oracle表名区分大小写的问题

    oracle不是区分大小写的,是建表的时候是没有去掉双引号.   CREATE TABLE TableName(id number); //虽然写的时候是有大写和小写,但是在数据库里面是不区分的.   ...

  2. 【一天一道LeetCode】#29. Divide Two Integers

    一天一道LeetCode系列 (一)题目 Divide two integers without using multiplication, division and mod operator. If ...

  3. 我也来写DBUtils

    关于重复造轮子 作为一个程序员,我们不止一次听到师长前辈们说:不要重复造轮子,已经有现成的了,直接用就是了. 对于这个观点,我觉得得仔细分析分析. 如果我们正在做一个真实的项目,经理天天追在我们屁股后 ...

  4. OPEN A PO ORDER OR SO ORDER

    OPEN PO ORDER fnd_function.Execute(Function_Name => 'PO_POXPOEPO', Open_Flag => 'Y', Session_F ...

  5. 分布式版本库——Windows下Git的环境部署以及在GitHub上开源自己的项目

    分布式版本库--Windows下Git的环境部署以及在GitHub上开源自己的项目 这几天着实忙的焦头烂额,可惜不是搞技术,今天周日,难得闲下来,写篇大家都想学习的Git教程,其实廖雪峰老师的网站已经 ...

  6. x64系统的判断和x64下文件和注册表访问的重定向——补记

    原来的地址 x64系统的判断和x64下文件和注册表访问的重定向(1) x64系统的判断和x64下文件和注册表访问的重定向(2) x64系统的判断和x64下文件和注册表访问的重定向(3) 之前在(3)里 ...

  7. java--GUI(图形用户接口)

    转载请申明出处:http://blog.csdn.net/xmxkf/article/details/9795435 day22 01-GUI(概述) GUI(图形用户界面) 1. GUI(Griph ...

  8. Log4j运用于代码中

    在JAVA代码中,我们要打印输出语句的时候,我们经常会使用System.out.print(),但是在项目开发完后,这些代码就会影响项目的运行效率,所以Log4j就派上用场了.话不多说,直接上代码. ...

  9. javascript中的instanceof运算符

    instanceof运算符希望左操作数是一个对象,右操作数表示对象的类:如果左侧的对象是右侧类的实例,则返回true,否则返回false.由于js中对象的类是通过初始化它们的构造函数来定义的,因此in ...

  10. OpenShift实战(七):OpenShift定制镜像S2I

    1.基础镜像制作 由于公司的程序是Java开发,上线发布使用的是maven,如果使用openshift自带的S2I,每次都会全量拉取代码(代码比较多,每次全量拉太慢),然后每次打包都会再一次下载mav ...