代码改变世界

三言两语话委托

2010-05-16 16:18 by FantasySoft, ... 阅读, ... 评论, 收藏, 编辑

几天前,Michael向大家推荐了刚发布的IronPython Tools for Visual Studio。不知道这个消息是否能够为大家学习IronPython注入少许动力呢?有了IDE的支持,Michael的学习热情也随之高涨了,把若干年前的随笔翻箱倒柜的看了个遍,那个感慨啊……对技术充满激情的岁月或许再也回不去了。咳,别感春悲秋嘛,这可是在写技术随笔哦!好吧,让我们回归正题。

在翻箱倒柜过程中,我看到了在2007年2月发表的《不谈模式,只谈实现》。这是受到Justin一篇美文的启发,我所写的当年唯一一篇有关程序设计的文章。对于这篇随笔,老赵给我留下了这样的评论:

“这就是动态语言啊,相当于保留函数指针。
C#里面很像函数指针的是什么的呢?就是delegate。
而delegate的典型应用是什么呢?就是事件机制。
那么Java里的事件机制是怎么做到的呢?就是定义EventListener然后实现相应的方法。这就是和那个OO的Duck类似的实现了,一个Duck和一个EventListener在这个方面有些接近。
如果不熟悉Java的Event Listener的话也可以用传统的Observer模式来看,而且其实Event Listener等等,其实不就是用了Observer模式嘛。”

说实在话,由于自己水平有限,偶对这个评论并不能完全理解,特别是老赵提到了我并不是非常熟悉的delegate。到三年后的今天,偶再次阅读这条评论的时候,不得不由衷赞叹:老赵的评论字字珠玑!对于偶这个Java开发者来说,委托(delegate)是一个相对陌生的概念,即便是动态代理也并非委托在Java世界里的孪生兄弟。事实上,Java语言为什么能够如此流行,抛弃了难以驾驭的指针,降低了学习门槛是重要原因之一。但是,没了指针,也直接导致无法使用函数指针,这让很多C++开发人员使用Java就等于丧失了大半功力。C#作为Java后来者,在灵活性和简单之间取得了良好的平衡点,譬如,在方法参数上,Java只能传值,而C#既可以传值也可以传引用。对于函数指针,C#则创造了新的机制与之对应——那就是委托了。如果您和我一样,对委托了解得不够深入的话,建议您将JimmyZhang的经典文章——《C# 中的委托和事件》反复研读几遍,就会对委托有充分的了解了。如果您也和我一样,对为什么需要委托也心存疑虑的话,建议您阅读老赵的美文——《高阶函数、委托与匿名方法》,这也是一篇值得反复品味的文章。看了上述两篇文章之后,我们可以得出这样的结论:委托类型实现了强类型函数指针的功能,通过委托类型,我们可以将某个函数封装起来作为另外一个函数的参数。这个特点在函数编程语言(譬如IronPython)中则不是什么新鲜事了,对照《C# 中的委托和事件》中的第一个完整的范例,使用IronPython实现之: 

>>> def EnglishGreeting(name):
...     
print("Morning," + name)
...
>>> def ChineseGreeting(name):
...     
print("早上好," + name)
...
>>> def GreetPeople(name, MakeGreeting):
...     MakeGreeting(name)
...
>>> GreetPeople("Jimmy Zhang", EnglishGreeting)
Morning,Jimmy Zhang
>>> GreetPeople("张子阳", ChineseGreeting)
早上好,张子阳

由于IronPython是一门动态语言,并且支持函数编程范式,所以我们可以直接将函数(方法)作为参数传递给另外一个函数(方法)。而在C#中,要实现这一点就需要使用委托类型了。

接下来,既然C#创造了委托类型,那么它的应用又在哪呢?是的,正如老赵的评论所言,事件机制是委托最典型的应用。有很多朋友都会觉得委托的使用有些多此一举,直接调用不就好了,那么兜兜转转干嘛呢?实际上,如果您希望直接调用也没啥问题,但是当需求改变的时候,您发现代码有无数的地方需要修改,那个时候就估计一个头两个大了。通过委托,我们可以更好地实现Observer模式,而这个模式定义了对象之间一对多的依赖关系,当一个对象改变了状态,那么所依赖的多个对象就会收到通知并且更新状态。要更好地了解Observer这个模式,除了《C# 中的委托和事件》中的讲解之外,仍旧是JimmyZhang的力作——《重温Observer模式--热水器·改》。文中给出了不使用委托实现Observer的方法,大家可以对比一下,繁简立现!看到文中IObservable接口的Register(IObserver obj)和Unregister(IObserver obj)方法,大家会联想到什么?是的,就是在.NET事件机制的典型应用当中,经常出现的+=和-=操作符,它们的作用在于更改委托对象所引用的方法列表。如果不使用委托,那么我们并不能直接对引用的方法列表进行操作,而要面向接口进行编程了。而对于IronPython来说,既然函数是一等公民,可以作为方法的参数,那么通过list的append方法就能轻松操作引用的方法列表了。参照JimmyZhang的两篇文章,我们也可以使用IronPython实现类似的热水器功能: 

def makeAlarm(temperature):
    
print("Alarm: " + str(temperature))
            

def showMsg(temperature):
        
print("Display: " + str(temperature))
            

class Heater:
    
def __init__(self):
        self.event 
= []
    
def boilWater(self):
        
for i in range(1,100):
            self.temperature 
= i
            
if self.temperature > 95:
               [m(self.temperature) 
for m in self.event]
               

heater 
= Heater()
heater.event.append(makeAlarm)
heater.event.append(showMsg)
heater.boilWater()

上述代码中的[m(self.temperature) for m in self.event]使用了IronPython强大的列表内涵特性,self.event是一个list,其中的元素就是makeAlarm和showMsg这两个函数。通过m(self.temperature)就能够以self.temperature为参数调用上述两个函数了。大家是否觉得使用IronPython所编写的代码会更加短小精悍呢?

综上所述,如果您不明白什么是委托,那么您可以把它看作强类型的函数指针;如果您不知道什么是函数指针,那么您可以把它看作将函数(方法)作为参数传递的一种机制;如果您不了解为什么需要委托,那么您可以参考一下使用与不使用委托来实现Observer模式之间的区别;如果您不知道为啥需要Observer模式,那么您可以进而了解一下Law of Demeter;如果您仍然不知道为啥需要遵循Law of Demeter,那么您就可以思考一下封装性为啥是面向对象编程核心要素之一了。