论坛首页 编程语言技术论坛

闭包(closure)--Martin Fowler

浏览 2887 次
精华帖 (0) :: 良好帖 (1) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2008-07-11  
如有任何疑问,请参阅:http://www.martinfowler.com/bliki/Closure.html

随着人们对动态语言兴趣的增加,更多的人开始关注一个被称之为闭包(closures)或者块区(blocks)的概念,
有c/c++/java/c#(这些语言不支持闭包)背景的程序员并不知道闭包是什么。下面简单的解释了这个概念。

闭包出现迄今为止有一段时间了,我第一次使用闭包是在SmallTalk上,当时被称之为块(block),Lisp使用了闭包,他们同样出现在Ruby脚本语言之中,这也是大部分Ruby爱好者使用Ruby进行脚本编程的原因。

闭包的核心意义是一段可以作为参数被其他函数调用的代码,我将用一个简单的例子来说明,假设我有一批工人的名单,而我想得到其中同时又身为管理人员的那部分工人的名单,对于这部分工人我用一个isManager来标记,使用C#,我们可以这样来编码:
public static IList Managers(IList emps) {
    IList result = new ArrayList();
    foreach(Employee e in emps)
      if (e.IsManager) result.Add(e);
    return result;
  }

而在支持闭包的语言中,我们可以用另一种写法,比如在Ruby中,我们可以这样:
def managers(emps)
  return emps.select {|e| e.isManager}
end
select是定义在Ruby集合类中的一个方法,接受一段代码,一个闭包,作为它的参数,在ruby中你(In ruby you write that block of code between curlies (not the only way))。如果这段代码接受任何你在事先在“|”中间声明的参数,select迭代作为参数的数组,对数组中每一个元素执行那一段代码,将所有返回结果确定为true的元素放入一个数组中,然后返回这个数组。

看完了这个例子,如果你是一名C程序员你可能会觉得你能够使用指针做同样的事情,如果你是一名JAVA程序员你会认为你能够使用匿名内部类实现,如果是C#程序员的话你可能会考虑代理(delegate),这些机制类似于闭包,但是他们有2个不同的地方。

首先是形似上的不同,闭包的内部可以拥有一个本地变量,考虑下面定义的方法:
def highPaid(emps)
  threshold = 150
  return emps.select {|e| e.salary > threshold}
end

你可以看到在select代码块中有一个变量是在本方法中定义的,而在许多不支持闭包的语言中并不能像这样做,利用闭包你可以做更加有趣的事情,看看下面的函数:

def paidMore(amount)
  return Proc.new {|e| e.salary > amount}
end

这个函数返回了一个闭包,在个函数的行为依赖于你传递给它的参数,我可以穿入一个值作为参数来创建一个这样的函数。

highPaid = paidMore(150)

highPaid包含了一段代码(在Ruby中被称为Proc),这段代码根据测试参数对象的工资是否超过150而返回true或者false,我可以这样来使用它:

john = Employee.new
john.salary = 200
print highPaid.call(john)

highPaid.call(john)调用我们前面定义过的代码e.salary>amount,这这里amount是150,这是我们前面创建该Proc时定义好的,当我调用print函数的时候,即使150已经超出了这个范围,但这个绑定依然有效。(Even if that 150 value went out of scope when I issue the print call, the binding still remains.)

所以第一个关键的地方是:闭包是一段代码加上对它们所在环境的绑定,这是使得闭包同C中的函数指针和其它语言中类似机制(比如java中的匿名内部类和C#中的代理)区别开来的一个因素。(java中只有final匿名内部类可以访问本地变量)

如果你不经过大量的实践的话,第二个不同点看起来并不如第一个不同点那么明显,但照样是也是非常重要的原因。支持闭包的语言允许你用很少量的语法来定义它们,这点看起来并不重要,但我认为很关键。这是为什么频繁运用闭包的原因,看看LISP,SMALLTALK,或者RUBY代码,你将会看到到处都在使用闭包语法--比起其他的语言中使用类似语法要频繁得多。能够访问本地变量和绑定运行上下文是一个原因,但我认为更重要的原因是这样使得代码更加简单清晰。

我来举一个很恰当的例子,将一个SmallTalk闭包语法的例子引入JAVA中。起初大部分人,包括我在内,都会使用匿名内部类去取代SMALLTALK中的闭包代码块,但最终的代码往往会变得非常丑陋和混乱,所以我们不得不放弃。

就像软件行业中其他的术语一样,人们并不能给出关于闭包的精确定义。一些人认为闭包仅仅应用于一个包括对自身环境绑定的实际值(the term only applies to an actual value that includes bindings from its environment),就像前述中highPaid函数所返回的那个值,另一些人认为闭包是指有能力绑定所在环境的程序构造(Others use the term 'closure' to refer to a programming construct that has the ability to bind to its environment),This debate, an example of the TypeInstanceHomonym, is usually carried out with the politeness and lack of pedantry that our profession is known for.

我经常在RUBY中用到闭包,但是我并不倾向于创建Procs and pass them around.大多数时候我使用闭包是基于围绕CollectionClosureMethods ,类似于前面所看到的select方法,另一个常用法是围绕方法执行,例如当我们处理一个文件。
File.open(filename) {|f| doSomethingWithFile(f)}

这里有一个open方法打开文件,执行代码块,然后关闭文件,This can be a very handy idiom for things like transactions (remember to commit or rollback) or indeed anything where you have to remember to do something at the end. I use this extensively in my xml transformation routines.

闭包的这种用法比起人们在LISP和函数式语言中所做的,其实是用的很少的,但即使是使用得少,当我使用不支持他们的语言编程的时候,我还是非常思念他们。闭包,当你第一次遇见它时它显得微不足道,但很快你就会喜欢上它。
论坛首页 编程语言技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics