概念界定

在讲解代理模式之前,我们需要区分一下委托、代理、中介三者的概念,因为很多人可能并不清楚他们之间的区别,甚至认为没有区别。但是,如果对这三个概念没有清晰的界定,很可能会在学习的过程中一头雾水,可能会觉得代理模式跟谁都很像,跟谁都容易混淆。

委托(Delegate)

委托跟代理是相对的,通常我们说"A委托B办某事",也相当于在说"B代理A办某事",因此,委托和代理通常可以认为是等价的,这也是为什么很多时候代理模式也可以叫委托模式了,但是,在C#中,委托被赋予了新的含义,它是一种引用方法的类型,相当于C++里的函数指针。下面是委托的一个简单实现,但这只是为了说明委托在C#中新的含义,不是本文的重点,看看就好。

public delegate int Calc(int x, int y);
class Program
{
    static void Main(string[] args)
    {
        Calc calc = new Calc(Add);
        int result = calc(1, 2);
        Console.WriteLine(result);

        calc = Subtract;
        result = calc(1, 2);
        Console.WriteLine(result);
    }

    public static int Add(int x, int y)
    {
        return x + y;
    }

    public static int Subtract(int x, int y)
    {
        return x - y;
    }
}

代理(Proxy)

如下图所示,代理的作用就是代替D(被代理者)完成某一件事,A、B、C访问了代理就等同访问了D。看似说了句废话,其实不然,这里最关键的一点就是A、B、C本来是可以访问D的,但是由于D的懒惰,或者访问过程比较困难甚至受阻(比如线路被切断),因此才有了代理,有了代理后,A、B、C就不用再访问D了,代理会全权处理来自A、B、C一切请求。见他如见我就是代理,如钦差大臣,产品代理商,代购,租房代理等。
设计模式-代理模式-LMLPHP

中介(Mediator)

如下图所示,中介是在一组复杂的关系中牵线搭桥,使得A、B、C、D相互之间的交流变得简单,中介不能完全替代A、B、C、D中的任何一方,牵线搭桥之后,被牵线的双方还是要见面的,如租房中介,婚姻中介等,婚姻中介介绍相亲的双方认识,但你不能要求婚姻中介替你谈恋爱甚至结婚生子,但是代理可以。

设计模式-代理模式-LMLPHP

我们之所以区分不清楚,是因为生活中很多时候就没区分清楚,例如,我们很多时候认为租房中介就是租房代理,而事实并非如此,仅仅是因为很多时候一个机构同时具备了两种角色而已,简单总结一下:

  1. 委托和中介是等价的,只是主谓方向刚好发生了对调;
  2. 代理可以全权代理被代理者,被代理者自始至终都可以不用跟访问者交互,而中介只是牵线,最终被牵线的双方还是要交互的;
  3. 代理通常解决的是多对一的关系,而中介解决的是多对多的关系,见他如见我是代理,牵线搭桥是中介,好好体会一下其中的区别。

定义

书归正传,代理模式就是为其他对象提供一种代理以控制对这个对象的访问。

使用场景

Windows快捷方式,VPN,防火墙,RPC调用,翻墙等。

目的

  1. 在不改变原有代码的基础上,对原有类加以控制,如下加日志和异常捕获体现的就是一种访问控制:
    public interface IUserRepository
    {
        User Get(int id);
    }
    
    public class UserRepository : IUserRepository
    {
        private static readonly List<User> _users = new List<User>
        {
        new User{Id=1,Name="zs"},
        new User{Id=2,Name="ls" },
        new User{Id=3,Name="ww" }
        };
    
        public User Get(int id)
        {
           return _users
               .FirstOrDefault(u => u.Id == id);
        }
    }
    
        public class UserRepositoryProxy : IUserRepository
    {
        private readonly IUserRepository _userRepository = new UserRepository();
        private readonly ILogger<UserRepositoryProxy> _logger;
        public UserRepositoryProxy(ILogger<UserRepositoryProxy> logger)
        {
            this._logger = logger;
        }
    
        public User Get(int id)
        {
            try
            {
                _logger.LogDebug("UserRepositoryProxy-Get In:id={0}", id);
                return _userRepository.Get(id);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "UserRepositoryProxy-Get Error.");
                throw;
            }
    
        }
    }
    
  2. 访问由于某种原因不能直接访问或者直接访问困难的第三方组件或中间件,如下面的HTTP请求:
    public interface IUserProxy
    {
        string GetUserById(int id);
    }
    
    public class UserProxy : IUserProxy
    {
        public string GetUserById(int id)
        {
            using (var client = new HttpClient())
            {
                var result = client
                    .GetAsync($"https://localhost:5001/user?id={id}")
                    .GetAwaiter()
                    .GetResult();
                return result.Content
                    .ReadAsStringAsync()
                    .GetAwaiter()
                    .GetResult();
            }
        }
    }
    

UML类图

设计模式-代理模式-LMLPHP

代理模式通常采用组合的方式实现,因为被代理者往往不希望被客户端直接访问,当然,也并不是任何时候都有明确的被代理对象,例如上面的HTTP请求就不知道具体代理的是谁。但是,可以很明显的看出,这里是代理模式,代理的是对网络API接口的请求。因此,代理模式重在思想,而并非代码结构,如果某种场景代码结构和其他模式类似,或者和上面的UML类图完全不同也不用觉得奇怪。

和适配器比较

代理模式和适配器模式看似都是在两个对象之间建立桥梁,使二者可以相互通信,因此他们的代码结构有时候是一样的,但是他们之间有明显的区别:

  1. 适配器模式的目的是接口转换,使原本不兼容而不能一起工作的类可以一起工作;
  2. 代理模式的目的是间接访问和访问控制;
  3. 适配器模式面向的是不能一起工作的两个类,而代理模式是面向原本可以一起工作的两个类。

总结

随着系统复杂度的发展,代理模式更偏向于是一种架构模式,在各种框架中以及与各种中间件交互是是非常常见的,而在我们自己的代码中反而很少见了,它更多的体现的是一种思想,而非代码实现。相对于如何实现代理模式,更重要的应该是什么时候什么场景应该使用代理模式,知道了什么时候什么场景使用,就不会纠结实现出来的像适配器模式还是像装饰模式了,即使像也仅仅是长得像而已,本质是完全不同的。

源码链接

08-31 20:35