正如其名,代理模式就是为其他对象提供一种代理以控制对这个对象的访问。理解起来很简单,就是提供一个代理对象,让代理对象代理执行实际对象中的方法。说穿了,代理类就相当于我们生活中的中介。而本篇将分为两个小节,分别是静态代理和动态代理。通过这两个小节,循序渐进的学习代理模式。
一、静态代理
举个简单的例子。在打官司的过程中,我们是原告。我们要请个律师来代理我们进行辩护,而这个律师就是我们的代理人。 (代理律师:根据当事人的委托代表当事人进行法律活动的代理人。)
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);
}
这就是一个最简单的代理模式的应用,我们通过律师(代理类)来限制了对原告(具体执行类)的访问。来看看代理模式的经典类图:
但是这样的代理还存在一个很大的问题,现实生活中,律师不可能是一对一专门为某个人服务(一般情况下),一个律师是可以为不同的对象服务的。在编写程序时,也不可能每写一个代理,都要去新建一个代理类。所以我们来修改一下代理类(律师类),让这个律师不仅仅可以代理我们进行辩护。
/// <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();
}
}
很简单,只需要将被代理人作为构造函数参数传入代理类即可。就相当于是告诉代理律师要代理某某人。
看到这里,很容易就能分析出代理模式的有优点。
· 职责清晰,真实对象负责真实的业务逻辑实现,代理人负责进行调用以及处理代理对象各种前期和后期工作。(注意:代理人并不是只能代理一个真实对象,只是上面的例子没有体现而已)
· 访问控制,可以对真实对象进行访问控制。
二、动态代理
其实我们不难发现,静态代理仍然不够灵活。在不同类型的的对象进行代理时,任然需要创建对应的代理类。既然有问题,那么就要解决问题,所以就有了动态代理。动态代理的核心就是不用去关心谁是代理,需要进行代理的时候,动态的创建一个即可。
代理模式的核心是代理人并没有对代理的对象做任何修改,只是“拦截”了代理对象的方法,在执行代理方法的前后多干了一些事而已。既然如此,我们就可以根据这种思路来实现动态代理,通过一个定义和实现一个拦截类,在执行真实类中方法时,先执行拦截对象中的方法。
还是上面那个例子,不过这次不是我们去找代理律师了。现在法院提供了法律援助服务,我们只需要将被告告到法院,由法院去为我们提供代理律师。
继承拦截器接口,重写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);
}
运行一下。
通过运行结果可以看到,在执行真实类中Say方法之前执行了拦截器中的Before方法(其实也执行了After方法,不过After方法中没有代码)。动态代理类是通过Emit放射实现的,原理是继承真实类并重新构建Say,在执行基类(真实类)中Say方法前后执行拦截对象中的方法。有兴趣可以参考文末的参考链接和下载代码查看,本篇暂不讨论如何去实现动态代理。
目前非常流行的一种编程方式叫做面向切片编程(AOP),其核心就是动态代理。AOP其实是对面向对象编程的一种补充,它更关心的一个系统中横切面,比如权限控制,系统日志等。如果写过ASP.NET或ASP.NET Core的拦截器的话,就很容易明白AOP编程的思想了。
代码下载:点击下载
参考
1、Emit学习-基础篇-为动态类添加属性、构造函数、方法
2、Emit实现动态代理
3、FastIOC动态代理框架
4、什么是AOP
5、《设计模式之禅》 -秦小波
文章评论