作为c++开发者,也许您可以经常听到java/.net开发者将“委托”、“事件”挂在嘴边,但到底什么是委托?委托会给我们的程序代码什么好处呢?阅读了本文,也许您的问题将得到解答。
(请注意,本文中的委托主要针对开发语言意义上的委托,并非特指设计模式中的委托)
故事的开始,在某年某月某日某地的一块空地上,来一群程序员,他们在空地上建了一个小作坊A,专门买进布料,做成衣服卖出去,从此开始了他们简单而又快乐的生活,这时候这个小作坊叫做函数(function)。
渐渐地小作坊A的工作量越来越多,程序员们发现如果不进行整理,整个小作坊里将会越来越乱,而且效率也很低。于是,程序员们把原来的函数变成了多个函数组成的流水线,1号函数车间负责清理原料,然后把处理过的原料送给2号函数车间制造成衣服,最后成品被3号车间检验、包装、发出厂,整理之后,小作坊的责任清晰了,效率也提高了,程序员们又找回了快乐的生活。
又过了两年,小作坊A已经变成了一个大工厂A,内部车间也因为分工的细化,变成了几十上百个车间,好像每个车间事情都类似,又好像有所差别。没有谁知道一个工序应该从哪个车间进来,完成后又要从哪个车间出去,程序员们发现他们陷入了面向过程的泥潭。于是程序员们决心再次对工厂进行重新规划,他们根据面向对象原则,把工厂分为研发、生产、营销、后勤四个部门对象(Object),每个部门管理自己的事物及下属车间,而部门之间通过public接口互相协调,程序员们只需要关心产品如何在各部门之间流转,各部门会自己管理具体细节,这样一来,程序员们的生活又重新变得简单了。
面向对象的引入极大的提高了工厂的生产效率,随着业务量的增多,A工厂变成了一个大公司A,而且开始在各地增加了业务办事处。各地的业务员们在谈生意的过程中,经常要向工厂部门了解信息,如是否有存货等,可是办事处并不是在工厂内部,无法走路到隔壁部门查询。程序员们为了解决这个问题,为各部门安装了专用电话,外地业务员只要直接拨打各部门电话即可查询到工厂当前的情况,我们把这种电话称为公开接口(public interface)。
当A公司的业务量多到原有工厂已经无法承担的时候,A公司决定在人力成本便宜的外地新开一个工厂,这样S公司就可以生产更多的衣服。我们把原工厂叫做F1,新工厂叫做F2。新工厂开门没几天,程序员们又发现了问题:以前一个工厂可以有多少订单做多少货,但是同时拥有两个工厂,必须协调每个工厂的生产量。因此,新工厂F2生产前必须打电话获知旧工厂F1的生产量,再来决定自己要生产多少。于是F2的厂长打电话给F1的生产部门:“我们准备进行生成,请告诉我你们准备生产多少衣服”,如果此时对方很清楚答案,那就可以马上获得结果,可是如果对方不确定,或者需要通过其他流程要经过一、两天才能获得结果,那F2的厂长肯定不可能在电话旁守上一两天等结果,而不去做其他事情。于是,程序员们设计了回调函数机制(callback function),F2厂长只需要告诉接电话的人:“请获得结果后通知F2厂长”,他就可以继续去做其他事情。当F1中某人(不一定是接电话的人)最后确认结果后,可以查询公司通讯录,找到F2厂长的联系方式并告知结果,F2厂长收到通知后,则安排工厂进行生产。很熟悉的场景,不是么?
终于有一天,A公司成为了上市公司 ,甚至变成了A集团,对外交流变得越来越频繁,不仅要接受记者采访、投资者咨询,还要处理客户回访与投诉等等事务。程序员们发现,原有的回调函数机制,对于通讯录存在的已知用户是有效的,但是当系统要对无数且不可预知的外部用户进行回调时,期待建立可覆盖所有未来外部用户的通讯录是不现实的,因此必须设计一种新的模式来处理这种场景。
为了应对上述挑战,聪明的程序员们发明了委托机制,他们为A集团设立了一个呼叫中心,所有用户的电话都集中到呼叫中心。当某外部用户需要获得一个处理结果时,呼叫中心会提供多种可能的通知方式,用户选择合适的通知方式,并将该方式所需的联系资料告知接线员。接线员收到任务后,负责在集团内部协调处理该任务,并且在处理完成后,通过用户告知的联系方式将结果通知用户。
一般来说,我们将多种可选的通知方式称为委托(delegate)模版,它定义一个有效的联系方式必须的信息;而我们将回调点称为事件(event),外部用户通过注册事件,将自己添加到通讯录中,以便在指定事件触发时(如任务完成)收到通知。广义上的委托,仅指一个类将某任务委托给另外一个类去完成,而事件、委托(可参考java的xxx_event / xxx_actionListener)则是委托机制的一个特例。实际上,同样是事件与委托,各厂商的实现方式也不太一样,比如java的委托是基于interface,而.net的委托是基于method,两者的差别还是相当大的,当然我个人是坚定支持.net的,原因看看下面的场景就知道了^_^
Sun呼叫中心
Sun 接线员:您好
用户:你好,请你过5分钟打我家电话好吗?我家的电话是12345678。
Sun接线员:请问您家的电话是继承自SunPhone话机吗?
用户:啊?不是,只是普通话机。
Sun接线员:那您家的电话是否实现了ISunPhoneActionListener接口?
用户:那是什么?要怎么确认?
Sun接线员:您可以检查您家的话机是否继承自ISunPhoneActionListener,并且全部实现了该接口的Phone_callout、Phone_callin、Phone_callclear、Phone_idle等接口,特别提醒您,所有接口函数的函数名与参数必须完全匹配。如果没有,我们建议您新增一个话机SunPhoneWarpper实现该接口,再进行注册,或者您也可以在我们这里购买一个临时匿名话机进行注册...
用户:......
M$呼叫中心
M$接线员:您好
用户:你好,请你过5分钟打我家电话好吗?我家的电话是12345678。
M$接线员:请问您的话机是否有方法接受一个来电号码作为输入参数?
用户:有的,SomeCallin(caller)
M$接线员:注册已成功,5分钟后您将接到电话。
用户:^_^