本文面向的阅读对象:
对委托的概念和实现方法有基本了解的同学
示例所使用的语言:
C#
刚接触面向对象编程时,就了解过委托这个概念,但由于见过的委托实例较少,且在日常开发中不曾使用委托,所以每次都是看过就忘。为了避免这样的悲剧再次发生,也为了给其他和我有同样苦恼的同学加深一下对委托这个概念的印象,故决定写一篇文章围绕问题核心——“为什么需要委托?”——对委托进行分析。
委托的特性
提到为什么,大家一定迫切想知道委托的应用场景。但在讨论委托的作用之前,我想先简单介绍一下委托的特性,这对理解委托的作用有一定的帮助。
让方法成为对象
刚接触委托的同学一定会发现,它最大的特点是能够让函数成为参数传到其他函数中去,这让人联想到C语言的函数指针。这两者在作用上非常相像,但在书写形式上,委托更像是(明明就是)对象,如下面的委托声明以及实例化代码。
delegate string DelegateDemo(int delArg); // 委托声明
string toBeDelegated(int number); // 被委托的方法
DelegateDemo delegateIt = new DelegateDemo(toBeDelegated); // 实例化委托
很多面向对象语言如C#、JS还可以用匿名方法、Lambda表达式来表示委托,这种代码形式总会初次接触的人感到困惑,开始怀疑委托究竟是个什么东西。遇到这种情况,个人以为最好的方案就是以万物皆对象的OO思路去理解它,即把委托当成一个类,把委托实例当成一个对象。这种想法也恰巧与C#、JS中对委托的处理方式相吻合。
规定了委托方法的参数与返回值
委托还有个特点是它规定了被委托方法的参数与返回值,这一点和函数指针几乎是一样的。这样设计的原因能从“委托”这两个字本身看出来。委托是负责接受其他方法委托的,这就要求其保证自身条件能够代表那些被委托的方法。方法最主要的表现形式就是参数与返回值,为了成为一位合格的“委托者”,委托就要在这两点上为自己定下严格的规矩,也就是这两者的类型。
这样的设计使得委托的职责与接口有些相似,但委托并不像接口那样要求被使用者强制实现,委托的使用更为灵活。
委托所解决的问题
介绍完了委托的特性,接着讲讲委托的实际应用场景。由于本人对委托这一模式上手使用的次数很有限,以下分析有不对的地方还望各路大佬指正。
回调函数
回调函数是异步编程很重要的一个概念,了解JS的同学想必对它一定不陌生。如果要简单定义下回调函数的话,可以把它理解为一种作为参数放在其他函数里的函数,委托正是实现这一功能的不二之选。例子见下
// 委托与回调函数声明
delegate string DelegateDemo(int delArg);
void anotherMethod(DelegateDemo delegate);
// 将回调函数作为参数传入
string toBeDelegated(int number);
anotherMethod(new DelegateDemo(toBeDelegated));
这一设计的好处是把方法的一部分功能分离出来,让外部调用者自己规定部分功能,同时又能保证使用回调函数作为参数的这个函数不受回调函数具体功能限制,只需知道回调函数的参数与返回值类型即可。回调函数在一些标准库中就已经实现,比如C#中,Enumerable.Select方法。下面的例子为msdn上C#文档所提供
IEnumerable<int> squares =
Enumerable.Range(1, 10).Select(x => x * x); // x => x * x为匿名回调函数
foreach (int num in squares)
{
Console.WriteLine(num);
}
/*
This code produces the following output:
1
4
9
16
25
36
49
64
81
100
*/
其他语言或框架如C++的sort()函数与jQuery中各种异步函数都采用了回调函数的机制,来允许使用者自行规定一些规则。
事件
除了回调函数,委托所服务的另一个大头就是事件。这里请允许我偷个懒,直接搬运msdn上的解释,个人以为微软文档对事件的介绍已经比较全面了。链接在此。