【发布时间】:2010-11-21 07:00:15
【问题描述】:
作为OO 开发人员,我可能很难看到它的价值。他们提供了什么附加值?它们适合 OO 世界吗?
【问题讨论】:
标签: oop functional-programming closures
作为OO 开发人员,我可能很难看到它的价值。他们提供了什么附加值?它们适合 OO 世界吗?
【问题讨论】:
标签: oop functional-programming closures
你可以把它看作是一个类的泛化。
您的班级拥有某种状态。它有一些成员变量可供其方法使用。
闭包只是让函数访问本地状态的一种更方便的方式。
不必创建一个知道你希望函数使用的局部变量的类,你可以简单地在现场定义函数,它可以隐式访问当前可见的每个变量。
当你在传统的 OOP 语言中定义一个成员方法时,它的闭包是“所有在这个类中可见的成员”。
具有“适当”闭包支持的语言简单地概括了这一点,因此函数的闭包是“这里所有可见的变量”。如果“here”是一个类,那么你有一个传统的类方法。
如果“here”在另一个函数中,那么你就有了函数式程序员认为的闭包。您的函数现在可以访问在父函数中可见的任何内容。
所以这只是一个概括,消除了“函数只能在类中定义”的愚蠢限制,但保留了“函数可以看到在声明它们的点可见的任何变量”的想法。
【讨论】:
闭包不会给你任何额外的力量。
任何你可以通过他们实现的事情,你也可以在没有他们的情况下实现。
但是它们对于使代码更清晰和可读性非常有用。 而且我们都知道干净可读的短代码是一种更容易调试并且包含的错误更少的代码。
让我给你一个可能使用的简短 Java 示例:
button.addActionListener(new ActionListener() {
@Override public void actionPerformed(ActionEvent e) {
System.out.println("Pressed");
}
});
将被替换(如果 Java 有闭包):
button.addActionListener( { System.out.println("Pressed"); } );
【讨论】:
对我来说,闭包的最大好处是当您编写的代码可以启动任务、让任务继续运行并指定任务完成后应该发生什么。通常,在任务结束时运行的代码需要访问开始时可用的数据,而闭包使这变得容易。
例如,JavaScript 中的一个常见用途是启动 HTTP 请求。启动它的人可能想要控制响应到达时发生的情况。所以你会做这样的事情:
function sendRequest() {
var requestID = "123";
$.ajax('/myUrl', {
success: function(response) {
alert("Request " + requestID + " returned");
}
});
}
由于 JavaScript 的闭包,“requestID”变量被捕获在成功函数内部。这显示了如何在同一个地方编写请求和响应函数,并在它们之间共享变量。如果没有闭包,您需要将 requestID 作为参数传入,或者创建一个包含 requestID 和函数的对象。
【讨论】:
恕我直言,它归结为能够捕获代码块和它们的上下文,以便稍后在某个时候引用并在/如果/需要时执行。
它们可能看起来没什么大不了的,而且闭包绝对不是您每天都需要完成的事情 - 但它们可以使代码代码更简单、更清晰/管理。
[编辑 - 基于上述评论的代码示例]
Java:
List<Integer> numbers = ...;
List<Integer> positives = new LinkedList<Integer>();
for (Integer number : integers) {
if (number >= 0) {
positives.add(number);
}
}
Scala(跳过了类型推断和通配符等其他一些细节,因此我们仅比较闭包的效果):
val numbers:List[Int] = ...
val positives:List[Int] = numbers.filter(i:Int => i >= 0)
【讨论】:
很遗憾,人们不再在edu中学习Smalltalk;在那里,闭包用于控制结构、回调、集合枚举、异常处理等等。 举一个很好的小例子,这是一个工作队列操作处理程序线程(在 Smalltalk 中):
|actionQueue|
actionQueue := SharedQueue new.
[
[
|a|
a := actionQueue next.
a value.
] loop
] fork.
actionQueue add: [ Stdout show: 1000 factorial ].
对于那些不会阅读 Smalltalk 的人,JavaScript 语法也是如此:
var actionQueue;
actionQueue = new SharedQueue;
function () {
for (;;) {
var a;
a = actionQueue.next();
a();
};
}.fork();
actionQueue.add( function () { Stdout.show( 1000.factorial()); });
(嗯,如您所见:语法有助于阅读代码)
编辑:注意如何从块内部引用 actionQueue,即使对于分叉的线程块也有效。这就是使闭包如此易于使用的原因。
【讨论】:
这里有一些有趣的文章:
【讨论】:
闭包非常适合 OO 世界。
以 C# 3.0 为例:它具有闭包和许多其他功能方面,但仍然是一种非常面向对象的语言。
根据我的经验,C# 的功能方面倾向于保留在类成员的实现中,而不是我的对象最终暴露的公共 API 的一部分。
因此,闭包的使用往往是其他面向对象代码中的实现细节。
我一直在使用它们,因为我们的一个单元测试(针对 Moq)中的这段代码 sn-p 显示:
var typeName = configuration.GetType().AssemblyQualifiedName;
var activationServiceMock = new Mock<ActivationService>();
activationServiceMock.Setup(s => s.CreateInstance<SerializableConfigurationSection>(typeName)).Returns(configuration).Verifiable();
如果没有 C# 的闭包特性,将输入值 (typeName) 指定为 Mock 期望的一部分将非常困难。
【讨论】:
关于结论性查询:“(闭包)是否适合 OO 世界?”
在许多语言中,闭包以及一流的函数为设计 OO 程序提供了非常基础:可以想到 Javascript、Lua 和 Perl。
在我熟悉的另一种更“传统”的 OO 语言 Java 中,有两个主要区别:
因此,在诸如 Java 之类的“传统 OO”语言中,添加闭包在很大程度上只是语法糖。
【讨论】:
也许在编译编程的世界中,闭包的好处不太明显。在 JavaScript 中,闭包非常强大。这有两个原因:
1) JavaScript 是一种解释性语言,因此指令效率和命名空间保护对于大型程序或评估大量输入的程序更快、响应更快的执行非常重要。
2) JavaScript 是一种 lambda 语言。这意味着在 JavaScript 中,函数是定义范围的第一类对象,并且子对象可以访问父函数的范围。这很重要,因为变量可以在父函数中声明,在子函数中使用,并且即使在子函数返回后也可以保留值。这意味着一个变量可以被函数多次重用,其值已由该函数的最后一次迭代定义。
因此,闭包非常强大,并且在 JavaScript 中提供了卓越的效率。
【讨论】:
看看上面的例子,我可以补充一下。
我欣赏风格、状态隐藏部分和表达链,但这还不够
关于简单明确的代码可以说很多
我会认为 python 的简单性和 java 的早期语法等作为简单性和明确性的更好方式
在狭小的空间中编写神秘的链式闭包可能会非常快。然而一切都可以通过简单的对象和算法来完成
闭包是必要的,但并不经常保证它们是一等公民。
【讨论】: