深入理解回调 CallBack
相信大家都知道委托和事件,没错,委托和事件是用来传递和保存函数用的,那么 回调 呢,近段时间的学习中经常碰到 回调,通过查阅相关资料,我发现回调对理解委托是很重要的。简单的说,回调就是把方法当参数传到另外一个方法里面,传的过程中会用到委托,而回调的使用能抽离一个函数中不同的部分,使程序变得更加简洁和灵活。
举个例子:
假设我需要你帮我做一件事情,我有两个选择:
1. 把这个事情交给你,然后一直等到你做完这个事
2. 把这个事情交给你,然后继续去做我的事,等你做完了后,你通知我下你做的结果
好了,明白了callback,现在自然的思路就是:我是A, B委托让我在我这边的某事件发生后callback他,好的,到时个我A这边事件发生了,我就直接调用B指定的那个函数,callback完毕!要注意这个事件发生的次序,B委托A,然后A中的事件发生,然后A调用了B的函数。有三个部分组成!
但是这种设计方式是不好的!问题出在A直接call了委托者。这时假如要增加一个需要通知的对象,那就要改变A对象的内容,再增加一个,再改变。同样,当一个对象需要减除接受这个通知时,又要改变A对象的内容,要在A对象中维护这个需求,实在是不爽,因为对A来说,跟其它对象的耦合度太高了。那怎么办呢?
引入一个第三方对象X,它维护一个需要通知的列表,任何对象需要得到通知的时候,就告诉X而不是去直接委托A。而做为A,本身不需要关心要通知到哪些对象,只需要告诉X,事件发生了。该通知哪些对象是你X的事。但你会问,那这样不是把维护通知者列表的复杂性转移到C对象上去了么?并且还好端端多出一个对象来,岂不是把事情搞得更复杂了?并不是的,这儿其实就相当于把一个复杂易变的功能从A中剥离出来,A只负责做好他的更重要的工作,关注于事件本身,而不需要关注事件发生了,要通知哪些对象。这样,确实是降低了整个系统的耦合度。并且有时候会有一种情况:想接受到通知的某个对象,并不想让A知道他侦听了这个事件!!!这时候,更是需要第三方对象X的存在。
这时候我就大致明白 callback 以及如何实现一个设计良好的 callback 了,然后,callback与委托又有什么关系呢?委托就是用来方便地实现callback的手段!!!
这时我们考虑这样一个模拟情景,涉及到两个对象,一个员工,一个Leader在做一个项目,项目一开始时Leader就告诉员工,每当员工的完成一个TASK的时候,就要向LEADER报告,由LEADER来REVIEW并更新WBS。这是一个典型的callback事件。根据我们前面的考量,我们知道,不能由员工直接通知Leader,这是不好的设计,比如说:某天忽然Manager心血来潮,想密切关注该项目的进度,但他又不想让员工知道这个,怕给员工增加压力。(真是好领导呀~~~)。所以,我们买了一个机器人,当员工每完成一个TASK,员工就踢机器人一脚,机器人就会跑去通知Leader。当经理需要关注进度时,就给机器人下指令,员工踢你时你也要告诉我。这样,问题就都解决了。设计如下:
代码1
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace DelegateTest1
{
//职员类
class Employee
{
//踢机器人 完成工作
public void KickRobot(Robot r)
{
Console.WriteLine("I have finished a task!");
r.NoticeOthers();
}
}
//领导类
class Leader
{
//领导的反应
public void AcceptNotice()
{
Console.WriteLine("Yes,i will check it!");
}
}
//经理类
class Manager
{
//经理的反应
public void AcceptNotice()
{
Console.WriteLine("UMM,good boy!");
}
}
//机器人类
class Robot
{
//通知列表
private List<object> NoticeList=new List<object>();
//通知列表所有人
public void NoticeOthers()
{
foreach (object s in NoticeList)
{
if (s is Leader)
{
((Leader)s).AcceptNotice();
}
if (s is Manager)
{
((Manager)s).AcceptNotice();
}
}
}
//添加进列表
public void AddToList(object a)
{
NoticeList.Add(a);
}
}
class Program
{
static void Main(string[] args)
{
Employee XiaoLi = new Employee();
Leader LaoLiu = new Leader();
Manager AnZong = new Manager();
Robot WillSmith = new Robot();
WillSmith.AddToList(LaoLiu);//Only leader LaoLiu want to know that.
XiaoLi.KickRobot(WillSmith);
Console.WriteLine(" ");
WillSmith.AddToList(AnZong);//The Manger suddenly want to know that too.
XiaoLi.KickRobot(WillSmith);
}
}
}
仔细考虑上面的代码,我们“买”了一个Robot,花费了额外的“代价”,并且,这一流程有点绕弯子,是否可以直接在员工类中开放一个接口,需要监听者直接往这个接口里面放函数,到时我做了就OK。这时候,委托就派上用场了,如下:
代码2
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace DelegateTest2
{
//雇员类
class Employee
{
//定义通知目标的委托
public delegate void NoticeTarget(string msg);
//实例化这个委托
private NoticeTarget NoticeList;
//注册委托函数 这里用到了回掉
public void AddToNoticeList(NoticeTarget ExternalMethod)
{
NoticeList += ExternalMethod;
}
//完成工作了
public void Notice()
{
if (NoticeList != null)
{
NoticeList("I have finished a task!"); //回调
}
}
}
class Leader
{
public void AcceptNotice(string msg)
{
Console.WriteLine(msg+" Yes,i will check it!");
}
}
class Manager
{
public void AcceptNotice(string msg)
{
Console.WriteLine(msg+" UMM,good boy!");
}
}
class Program
{
static void Main(string[] args)
{
Employee XiaoLi = new Employee();
Leader LaoLiu = new Leader();
Manager AnZong = new Manager();
//注册领导和经理的反应函数
XiaoLi.AddToNoticeList(LaoLiu.AcceptNotice);
XiaoLi.AddToNoticeList(AnZong.AcceptNotice);
//这里实现了回调
XiaoLi.Notice();
}
}
}