设计模式(十二)代理

发布时间 2023-11-13 14:47:52作者: 冬先生

一、定义

给某一个对象提供一个代理或占位符,并由代理对象控制对原对象的访问。代理模式是一种结构型模式。

二、描述

代理模式的结构比较简单,其核心是代理类,为了让客户端能够一致性地对待真实对象和代理对象,在代理模式中引入了抽象层。包含以下三个角色:

1、Subject(抽象主题角色):它声明真实主题和代理主题的共同接口,这样一来在任何使用真实主题的地方都可以使用代理主题,客户端通常需要针对抽象主题角色进行编程。
2、Proxy(代理主题角色):它包含了对真实主题的引用,从而可以在任何时候操作真实主题对象;在代理主题角色中提供了一个与真实主题角色相同的接口,以便在任何时候都可以代替真实主题;代理主题角色还可以控制对真实主题的使用,负责在需要的时候创建和删除真实主题对象,并对真实主题对象的使用加以约束。通常,在代理主题角色中,客户端在调用所引用的真实主题操作之前或之后还需要执行其他操作,而不仅仅是单纯地调用真实主题对象中的操作。
3、RealSubject(真实主题角色):它定义了代理角色所代表的真实对象,在真实主题角色中实现了真实的业务操作,客户端可以通过代理主题角色间接调用真实主题角色中定义的操作。

三、例子

X公司公司承接了某信息咨询公司的收费商务信息查询系统的开发任务,该系统的基本需求如下:
(1)在进行商务信息查询之前用户需要通过身份验证,只有合法用户才能够使用该查询系统。
(2)在进行商务信息查询时,系统需要记录查询日志,以便根据查询次数收取查询费用。
X公司开发人员已经完成了商务信息查询模块的开发任务,他们希望能够以一种松耦合的方式向原有系统增加身份验证和日志记录功能,客户端代码可以无区别地对待原始的商务信息查询模块和增加新功能之后的商务信息查询模块,而且可能在将来还要在该信息查询模块中增加一些新的功能。
ISearcher:抽象查询接口,充当抽象主题类

public interface ISearcher
{
    string DoSearch(string userID, string keyword);
}

RealSearcher:具体查询器,充当真实主题类

public  class RealSearcher
{
    /// <summary>
    /// 模拟查询商务信息
    /// </summary>
    /// <returns></returns>
    public string DoSearch(string userID, string keyword)
    {
        Console.WriteLine("{0} 使用关键词 {1}", userID, keyword);
        return "返回具体内容";
    }
}

AccessValidator、Logger:身份验证类、日志记录类,业务类

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;
        }
    }
}

public class Logger
{
    /// <summary>
    /// 模拟实现日志记录
    /// </summary>
    /// <param name="userID"></param>
    public void Log(string userID)
    {
        Console.WriteLine("更新数据库,用户 {0} 查询次数加1!", userID);
    }
}

ProxySearcher:代理查询类,充当代理主题类

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);
    }
}

Program:客户端测试类

Searcher searcher = new ProxySearcher();
if (searcher != null)
{
    string result = searcher.DoSearch("杨过", "玉女心经");
}
Console.ReadLine();

四、总结

1、优点

(1)代理模式能够协调调用者和被调用者,在一定程度上降低了系统的耦合度。
(2)客户端可以针对抽象主题角色进行编程,增加和更换代理类无须修改源代码,符合开闭原则,系统具有较好的灵活性和可扩展性。

2、缺点

(1)由于这客户端的真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢,例如保护代理。
(2)实现代理模式需要额外的工作,而且有些代理模式的实现过程较为复杂,例如远程代理。