23种常见的设计模式(6):代理(委托)模式

1,457次阅读
没有评论

正如其名,代理模式就是为其他对象提供一种代理以控制对这个对象的访问。理解起来很简单,就是提供一个代理对象,让代理对象代理执行实际对象中的方法。说穿了,代理类就相当于我们生活中的中介。而本篇将分为两个小节,分别是静态代理和动态代理。通过这两个小节,循序渐进的学习代理模式。

一、静态代理

举个简单的例子。在打官司的过程中,我们是原告。我们要请个律师来代理我们进行辩护,而这个律师就是我们的代理人。 (代理律师:根据当事人的委托代表当事人进行法律活动的代理人。)
PS:我个人也没打过官司,具体流程也不是很清楚,大概明白就行。

下面我们来实现以上的这个过程。首先是具体的角色类,也就是我们的原告(真正干活的人)。

定义一个接口,指定一个Say方法。表示原告是要说话的,不能当哑巴。

public interface IUser
{
    void Say();
}

继承IUser,实现Say方法。

/// <summary>
/// 原告
/// </summary>
public class PlaintiffUser : IUser
{
    private string _name;

    public PlaintiffUser(string name)
    {
        _name = name;
    }

    public virtual void Say()
    {
        Console.WriteLine($"{_name}在讲述事实。(其实是代理人帮{_name}说的)");
    }
}

然后是我们的代理律师,他将代理我们说话,毕竟人家是专业的。和原告一样的,律师也要说话,如果律师都不帮我们说话了,那岂不是凉凉了。所以律师也要继承IUser类,并实现Say方法。

/// <summary>
/// 代理律师
/// </summary>
public class ProxyLawyer : IUser
{
    private IUser _myUser = null;

    public ProxyLawyer(string name)
    {
        if (string.IsNullOrEmpty(name))
            throw new Exception("被代理人姓名不能为空。");
        _myUser = new PlaintiffUser(name);
    }

    public void Say()
    {
        Console.WriteLine($"在说话之前已经进行过了认真的思考。");
        _myUser.Say();
    }
}

有了原告,有了律师。再来编写场景类。

/// <summary>
/// 代理模式
/// </summary>
/// <param name="args"></param>
static void Main(string[] args)
{
    string plaintiffUserName = "亚瑟";

    //静态代理
    IUser proxy = new ProxyLawyer(plaintiffUserName);
    proxy.Say();

    Console.ReadKey(false);
}
23种常见的设计模式(6):代理(委托)模式
静态代理运行结果

这就是一个最简单的代理模式的应用,我们通过律师(代理类)来限制了对原告(具体执行类)的访问。来看看代理模式的经典类图:

23种常见的设计模式(6):代理(委托)模式
代理模式通用类图

但是这样的代理还存在一个很大的问题,现实生活中,律师不可能是一对一专门为某个人服务(一般情况下),一个律师是可以为不同的对象服务的。在编写程序时,也不可能每写一个代理,都要去新建一个代理类。所以我们来修改一下代理类(律师类),让这个律师不仅仅可以代理我们进行辩护。

/// <summary>
/// 代理律师
/// </summary>
public class ProxyLawyer : IUser
{
    private IUser _myUser = null;

    public ProxyLawyer(IUser plaintiffUser)
    {
        if (plaintiffUser == null)
            throw new Exception("被代理人不能为空。");
        _myUser = plaintiffUser;
    }

    public void Say()
    {
        Console.WriteLine($"在说话之前已经进行过了认真的思考。");
        _myUser.Say();
    }
}

很简单,只需要将被代理人作为构造函数参数传入代理类即可。就相当于是告诉代理律师要代理某某人。

看到这里,很容易就能分析出代理模式的有优点。

· 职责清晰,真实对象负责真实的业务逻辑实现,代理人负责进行调用以及处理代理对象各种前期和后期工作。(注意:代理人并不是只能代理一个真实对象,只是上面的例子没有体现而已)
· 访问控制,可以对真实对象进行访问控制。

二、动态代理

其实我们不难发现,静态代理仍然不够灵活。在不同类型的的对象进行代理时,任然需要创建对应的代理类。既然有问题,那么就要解决问题,所以就有了动态代理。动态代理的核心就是不用去关心谁是代理,需要进行代理的时候,动态的创建一个即可。

23种常见的设计模式(6):代理(委托)模式
动态代理类图

代理模式的核心是代理人并没有对代理的对象做任何修改,只是“拦截”了代理对象的方法,在执行代理方法的前后多干了一些事而已。既然如此,我们就可以根据这种思路来实现动态代理,通过一个定义和实现一个拦截类,在执行真实类中方法时,先执行拦截对象中的方法。

还是上面那个例子,不过这次不是我们去找代理律师了。现在法院提供了法律援助服务,我们只需要将被告告到法院,由法院去为我们提供代理律师。

继承拦截器接口,重写Before或者After方法。

/// <summary>
/// 拦截器
/// </summary>
public class PlaintiffInterceptor : IInterceptor
{
    public override void Before(object @object, string MethodName, object[] Parameters)
    {
        Console.WriteLine("在说话之前已经进行过了认真的思考。");
    }

    public override void After(object @object, string MethodName, object Result, DateTime Start, DateTime End)
    {

    }
}

在情景方法中使用动态代理。

/// <summary>
/// 代理模式
/// </summary>
/// <param name="args"></param>
static void Main(string[] args)
{
    string plaintiffUserName = "亚瑟";

    //动态代理
    IUser proxy = new DynamictProxyLoader().CreateProxyObject<PlaintiffUser>(new PlaintiffInterceptor(), plaintiffUserName);
    proxy.Say();

    Console.ReadKey(false);
}

运行一下。

23种常见的设计模式(6):代理(委托)模式
动态代理运行截图

通过运行结果可以看到,在执行真实类中Say方法之前执行了拦截器中的Before方法(其实也执行了After方法,不过After方法中没有代码)。动态代理类是通过Emit放射实现的,原理是继承真实类并重新构建Say,在执行基类(真实类)中Say方法前后执行拦截对象中的方法。有兴趣可以参考文末的参考链接和下载代码查看,本篇暂不讨论如何去实现动态代理。

目前非常流行的一种编程方式叫做面向切片编程(AOP),其核心就是动态代理。AOP其实是对面向对象编程的一种补充,它更关心的一个系统中横切面,比如权限控制,系统日志等。如果写过ASP.NET或ASP.NET Core的拦截器的话,就很容易明白AOP编程的思想了。

代码下载:点击下载

参考

1、Emit学习-基础篇-为动态类添加属性、构造函数、方法
2、Emit实现动态代理
3、FastIOC动态代理框架
4、什么是AOP
5、《设计模式之禅》 -秦小波

afirefish
版权声明:本站原创文章,由afirefish2020-01-13发表,共计3150字。
转载提示:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(没有评论)
载入中...