接口在面向对象编程中应用极广。回调(CallBack)就是一个典型的示例。
先解释一下回调的概念。
通常情况下,我们创建一个对象,并马上直接调用它的方法。然而,在有些情况下,希望能在某个场景出现后或条件满足时才调用此对象的方法。回调就可以解决这个“延迟调用对象方法”的问题。这个被调用方法的对象称为回调对象。
实现回调的原理简介如下:
首先创建一个回调对象,然后再创建一个控制器对象,将回调对象需要被调用的方法告诉控制器对象。控制器对象负责检查某个场景是否出现或某个条件是否满足。当此场景出现或此条件满足时,自动调用回调对象的方法。
可以举个现实生活中的例子。
一读者想借《编程的奥秘——.NET软件技术学习与实践》这本书,但这本书已被其他读者借走了。于是,读者与图书馆管理员间发生了以下对话:
读者:“我把我的电话号码告诉你,等书一到就马上通知我。”
管理员:“好的。另一读者把书还回来后,马上给您打电话,书我先帮您留着。”
在上述这个场景中,读者就是“回调对象”,管理员就是“控制器对象”,读者的电话号码就是“回调对象的方法”,另一读者的还书事件就是“某一特定的场景”。
本节示例CallBackExamples展示了如何使用接口实现回调,此示例运行截图如图4-9所示。
图4-9 回调示例
程序运行时,从键盘上按任意一个键显示当前时间,整个程序可以“没完没了”地运行下去,除非您按了Esc键。
示例项目CallBackExamples的类图如图4-10所示。
ICallBack接口定义了一个run()方法。
//实现回调的类必须实现此接口
public interface ICallBack
{ void run();}CallBackClass类实现此接口,并在其run()方法中向控制台窗口输出当前时间。
图4-10 CallBackExamples示例项目类图
class CallBackClass:ICallBack
{public void run()
{ //输出当前时间
System.Console.WriteLine(DateTime.Now );
}
}Controller类中有一个私有的ICallBack类型的字段,用于存放回调对象的引用,此对象引用在构造函数中传入。
class Controller
{
public ICallBack CallBackObject = null;// 引用回调对象
public Controller(ICallBack obj)
{
this.CallBackObject = obj;
}
public void Begin()
{
Console.WriteLine("敲任意键显示当前时间,ESC键退出....");
while (Console.ReadKey(true).Key != ConsoleKey.Escape)
{
CallBackObject.run();
}
}
}
Controller类的Begin()方法启动整个处理过程。
调用代码如下:
class Program
{
static void Main(string[] args)
{
//创建控制器对象,将提供给它的回调对象传入
Controller obj = new Controller(new CallBackClass());
//启动控制器对象运行
obj.Begin();
}
}
可以看到,当示例程序运行时,何时调用CallBackClass对象的的run()方法是由用户决定的,用户每敲一个键,控件器对象就调用一次CallBackClass对象的的run()方法。在这个示例中,实现回调的关键在于ICallBack接口的引入。
读者可能在想,如果不用ICallBack接口,而直接使用CallBackClass对象,也可以实现同样的运行效果。
class Controller
{
public CallBackClass CallBackObject = null;//回调对象的方法引用
public Controller(CallBackClass obj)
{
this.CallBackObject = obj;
}
//……
}
但请仔细想一下,这样做的结果就使Controller类与CallBackClass对象绑定在一起,万一如果需要回调其他类型的对象,则必须修改Controller类的代码(本示例中至少得同时修改Controller类CallBackObject字段的数据类型与构造函数参数的数据类型两处代码)。
如果Controller类接收的是一个抽象的接口变量ICallBack,则任何一个实现了此接口的对象都可以被Controller类对象所回调,Controller类的代码可以保持稳定,无疑是一个好的设计方案。
事实上,接口可以看成是一个契约,它规定了某个对象必须“是什么样的”,即“接口所规定的方法,实现此接口的类一定有”。正是有了这种确定性,Controller类才成为一个用键盘来“发出”回调的“万能控制器”。
试一试:
(1)请读者再创建另外一个回调类,让Controller类回调它。
(2)请修改Controller类,让它一次可以回调多个对象或多种类型的对象。
提示:
(1)将Controller类的CallBackObject字段改为可容纳多个ICallBack对象的容器,比如数组、ArrayList等。
(2)利用本书13.1.3节中介绍的委托,也能实现同样的回调功能。
5.接口与抽象类的区别
小结一下接口与抽象类的区别。
q 抽象类是一个不完全的类,需要子类来完善它。
q 接口只是对类的约束,它仅仅承诺了类能够调用的方法。
q 一个类一次可以实现若干个接口,但一个类只能继承一个父类。
在实际编程中,接口的使用要比抽象类广泛得多。