答案 0 :(得分:59)
对象是穷人的封闭。
考虑Java。 Java是一种面向对象的编程语言,对真正的词法闭包没有语言级支持。作为一个解决方案的Java程序员使用匿名内部类,它可以关闭词法范围中可用的变量(前提是它们是final
)。从这个意义上说,对象是穷人的封闭。
关闭是穷人的对象。
考虑Haskell。 Haskell是一种函数式语言,对真实对象没有语言级支持。然而,他们可以使用闭包进行建模,如Oleg Kiselyov和Ralf Lammel的this优秀论文所述。从这个意义上说,封闭是穷人的对象。
如果你来自OO背景,你可能会发现对象更自然的思考,因此可能会认为它们是一个比封闭更基本的概念。如果你来自FP背景,你可能会发现闭包更加自然,因此可能会认为它们是一个比对象更基本的概念。
故事的道德是闭包和对象是彼此可表达的想法,没有一个比另一个更基本。这就是正在考虑的声明。
在哲学中,这被称为model dependent realism。
答案 1 :(得分:56)
关键是闭包和对象实现了相同的目标:在单个逻辑单元中封装数据和/或功能。
例如,您可以创建一个代表这样的狗的Python类:
class Dog(object):
def __init__(self):
self.breed = "Beagle"
self.height = 12
self.weight = 15
self.age = 1
def feed(self, amount):
self.weight += amount / 5.0
def grow(self):
self.weight += 2
self.height += .25
def bark(self):
print "Bark!"
然后我将类实例化为对象
>>> Shaggy = Dog()
Shaggy对象内置了数据和功能。当我调用Shaggy.feed(5)
时,他获得了一英镑。该磅存储在作为对象属性存储的变量中,这或多或少意味着它位于对象内部范围内。
如果我正在编写一些Javascript,我会做类似的事情:
var Shaggy = function() {
var breed = "Beagle";
var height = 12;
var weight = 15;
var age = 1;
return {
feed : function(){
weight += amount / 5.0;
},
grow : function(){
weight += 2;
height += .25;
},
bark : function(){
window.alert("Bark!");
},
stats : function(){
window.alert(breed "," height "," weight "," age);
}
}
}();
这里,我在一个函数中创建了一个范围,然后调用了该函数,而不是在一个对象中创建一个范围。该函数返回由某些函数组成的JavaScript对象。因为这些函数访问在本地作用域中分配的数据,所以不回收内存,允许您通过闭包提供的接口继续使用它们。
答案 2 :(得分:14)
最简单的对象只是在该状态下运行的状态和函数的集合。闭包也是状态的集合和在该状态下运行的函数。
假设我调用了一个回调函数。在这个回调中,我需要在函数调用之前对某个已知的状态进行操作。我可以创建一个体现此状态的对象(“fields”)并包含一个作为回调执行的成员函数(“method”)。或者,我可以采取快速简便的(“穷人”)路线并创建一个封闭。
作为对象:
class CallbackState{
object state;
public CallbackState(object state){this.state = state;}
public void Callback(){
// do something with state
}
}
void Foo(){
object state = GenerateState();
CallbackState callback = new CallbackState(state);
PerformOperation(callback.Callback);
}
这是伪C#,但在概念上与其他OO语言类似。正如您所看到的,回调类涉及到管理状态的大量样板文件。使用闭包会更简单:
void Foo(){
object state = GenerateState();
PerformOperation(()=>{/*do something with state*/});
}
这是一个lambda(同样,在C#语法中,但概念在支持闭包的其他语言中类似),它为我们提供了类的所有功能,而无需编写,使用和维护单独的类。 / p>
你也会听到一个必然结果:“对象是一个穷人的关闭”。如果我不能或不会利用闭包,那么我被迫使用对象来完成他们的工作,就像我的第一个例子。虽然对象提供了更多的功能,但是由于已经陈述的原因,闭包通常是闭包起作用的更好选择。
因此,没有对象的穷人通常可以通过闭包来完成工作,而没有闭包的穷人可以使用对象完成工作。富人同时兼顾每项工作。
答案 3 :(得分:6)
编辑:问题的标题不包括“反之亦然”所以我会尽量不去假设提问者的意图。
这两个共同阵营是功能性和命令式语言。两者都是可以通过不同方式以不同方式完成类似任务的工具。
闭包是穷人的对象。
对象是穷人的封闭。
单独地说,每个陈述通常意味着作者有某种偏见,通常以一种语言或一种语言与另一种语言或语言的不适为基础。如果不是偏见,它们可能受到一个环境或另一个环境的约束。我读到的作者说,这种事情通常是狂热者,纯粹主义者或语言宗教类型。如果可能的话,我会避免语言宗教类型。
闭包是穷人的对象。对象是穷人的封闭。
这位作者是“实用主义者”,也非常聪明。这意味着作者欣赏这两种观点并认识到它们在概念上是相同的。这是我的同伴。
答案 4 :(得分:5)
“对象是一个穷人的闭包”不仅仅是一些理论等价的陈述 - 它是一种常见的Java习语。使用匿名类来包装捕获当前状态的函数是很常见的。以下是它的使用方法:
public void foo() {
final String message = "Hey ma, I'm closed over!";
SwingUtilities.invokeLater(new Runnable() {
public void run() {
System.out.println(message);
}
});
}
这甚至看起来很像使用另一种语言的闭包的等效代码。例如,使用Objective-C块(因为Objective-C与Java相似):
void foo() {
NSString *message = @"Hey ma, I'm closed over!";
[[NSOperationQueue currentQueue] addOperationWithBlock:^{
printf("%s\n", [message UTF8String]);
}];
}
唯一真正的区别是该功能包含在Java版本的new Runnable()
匿名类实例中。
答案 5 :(得分:5)
只是这么多的糖,因为闭合物隐藏了裙子下面的匿名物品。
答案 6 :(得分:0)
对象可以用来代替闭包很容易理解,您只需将捕获的状态放在对象中并将调用作为方法。事实上,例如 C++ lambda 闭包是作为对象实现的(对于 C++ 来说,事情有点棘手,因为该语言不提供垃圾收集,并且由于捕获上下文的生命周期,因此很难正确实现具有可变共享状态的真正闭包)。< /p>
相反的(闭包可以用作对象)很少被观察到,但它是 IMO 一种非常强大的技术......考虑例如(Python)
def P2d(x, y):
def f(cmd, *args):
nonlocal x, y
if cmd == "x": return x
if cmd == "y": return y
if cmd == "set-x": x = args[0]
if cmd == "set-y": y = args[0]
return f
函数 P2d
返回一个闭包,它捕获了 x
和 y
的两个值。然后闭包提供使用命令读取和写入它们的访问权限。例如
p = P2d(10, 20)
p("x") # --> 10
p("set-x", 99)
p("x") # --> 99
所以闭包的行为就像一个对象;此外,由于任何访问都是通过命令接口进行的,因此很容易实现委托、继承、计算属性等。
这本好书 "Let Over Lambda" 建立在这个想法之上,使用 Lisp 作为一种语言,但是任何支持闭包的语言都可以使用这种技术(在 Lisp 中,您还可以使用宏来改变语法并读取宏来提高可用性并自动生成所有样板代码)。这本书的标题正是关于这个......一个let
包装一个lambda
:
(defun p2d (x y)
(let ((x x) (y y))
(lambda (cmd &rest args)
(cond
((eq cmd 'x) x)
((eq cmd 'y) y)
((eq cmd 'set-x) (setq x (first args)))
((eq cmd 'set-y) (setq y (first args)))))))
实际上,我不确定我是否同意这种方法中的“差”形容词。