封装和封闭有什么区别?

时间:2015-12-11 08:15:51

标签: scheme closures lisp

我真的不了解封装和封闭。我相信封装是不可更改的,除非它被代码更改。但是当我被要求解释如何将闭包和封装应用于代码时,我无法理解。

例如:

(define new-cercle #f)

(let ((n 0))
 (set! new-cercle
  (lambda (rayon)
    (begin
      (set! n (+ n 1))
        (lambda (msg)
           (cond ((eq? msg ’circonference)
                  (* 2 3.14 rayon))
                 ((eq? msg ’surface)
                  (* 3.14 rayon rayon))
                 ((eq? msg ’nb-cercles)
                  n)))))))

n是封装的,对吗?所以问题是:解释封装和闭包如何应用于此代码。

我不明白的另一件事是为什么let必须高于lambda?为什么当我把它放在lambda下面时,函数不能正常工作并且没有累加器?

(define acc
  (let ((n 1))
    (lambda (x)
      (set! n (* n x))
      n)))

我希望有人能够以一种简单的方式向我解释这一点,因为当我谷歌的时候,说实话,我对大多数主题都有的复杂例子并不了解。

2 个答案:

答案 0 :(得分:5)

封装模式的名称,涉及将某些相关项放在一个容器中然后随该容器一起旅行的任何情况,并通过该容器上的某些访问机制引用。这些项可以是运行时值,也可以是编译时标识符等等。由多个字段组成的对象封装字段:cons单元格封装carcdr。类封装了插槽。在一些对象系统中,方法也是如此。编译单元封装了它们的全局定义,例如函数和变量。

广泛使用"封装"在OOP中,是指将一个类定义为一个包含数据定义的单元,以及对其进行操作的方法:代码和数据是一个" capsule"。 (Common Lisp对象系统不是这样的:方法没有封装在类中。)

闭包是另一回事,非常具体:它是一个程序代码体,连同它的词汇环境,具体化到一个函数类型的对象中。在调用时,闭包的主体可以看到两组名称:闭包的函数参数,以及创建闭包的词法范围中的周围名称。闭包是封装的一个例子:它将代码体与词法范围封装在一起。进入胶囊的唯一方法是通过以下功能:该功能就像一个"方法",并且捕获的词汇环境的元素就像" slot"在一个对象中。

(通过组合代码和数据,Lisp闭包类似于流行的封装概念,而不是Lisp类对象。)

关于这个有趣的词:在计算机科学中," reify" 程序的某些方面是采取不是第一类对象的东西,并以某种方式将其转化为一。

几乎任何适用于理解程序的可识别概念都可能具体化。 (聪明的人必须提出一个关于如何做出明智的提议。)

例如,在给定执行点的整个未来计算可以具体化,并且生成的对象称为 continuation (并且更确切地说是无限延续) 。

当延续算子捕获未来的计算时,该未来变为假设:它实际上并未发生(不会执行)。相反,执行替代的未来,其中将继续返回给操作者的调用者,或者将其传递给调用者指定的函数。现在具有此持续性的代码可以使用它来显式调用原始的捕获的未来,就像它是一个函数一样。或者选择不这样做。换句话说,程序控制流程(执行此程序段或不执行此程序,或执行多次)已成为一个功能对象(调用此函数或不调用它,或多次调用它)

对象是具体化的另一个例子:模块的具体化。老式程序分为具有全局函数和全局变量的模块。这个"模块"结构是一个我们可以在程序中识别的概念,并且有用地应用于描述这样的程序。它很容易被物化:我们可以想象,如果我们有一个运行时对象是" module",具有所有相同的属性:即包含函数和数据?并且,presto:基于对象的编程诞生了,具有多个同一模块实例化的优点,可能因为变量不再是全局的。

关于cerclerayon

首先,new-cercle的行为类似于对象的构造函数:它是一个可以从任何地方调用的全局函数。它维护了已构造对象的数量。只有该函数才能访问计数器,因此它是封装的。 (实际上不仅该函数可以访问它,而且还有代表圆实例的闭包!)这是类模块封装的一个例子。它模拟模块,如Modula-2语言和类似模块,例如在文件范围内带有static变量的C语言翻译单元。

当我们调用new-cercle时,我们必须为rayon参数提供参数。生成并返回一个对象。该对象恰好是作为词法闭包产生的函数。这个闭包捕获了rayon参数,从而封装了这个值:对象知道它自己的半径。我们可以反复调用new-cercle,并获取不同的圆圈实例,每个圆圈都有自己的rayon。此rayon不在外部可见;它打包在闭包内,只对该函数可见。

我们通过"消息"间接访问人造丝容器。容器上的API。我们可以使用消息符号surface调用该函数,并通过返回表面区域来回复。当前可用的消息都没有直接显示rayon,但我们可以为其提供访问者消息,甚至是更改半径的消息。甚至还有一条消息可以访问共享变量n,即圆形计数,其行为类似于对象系统中的类变量(静态槽):任何圆形实例都可以报告已构造了多少个圆。 (请注意,此计数会告诉我们当前存在多少个圈子:当圈子变为垃圾并被回收时,它不会减少:没有 finalization )。

在任何情况下,我们都有一个容器,除了通过接口外,其内容无法访问。该容器将代码和数据绑定在一起,因此它不仅是封装,而且可以说是流行的OOP意义上的封装。

答案 1 :(得分:1)

你或许有困难,因为在琐碎的情况下,一些分歧消失了。例如,(let ((n 1)) (lambda (x) n))(lambda (x) (let ((n 1)) n)都可以为您提供基本相同的功能。

在你的例子中

(define acc (let ((n 1))
              (lambda (x) (set! n (* n x)) n)))

letlambda的排序很重要。如果您将它们与(lambda (x) (let ((n 1)) ...互换,则每次调用此函数时,n将再次绑定到1。相反,你希望有一个位置n,它以值1开头,可以被你的函数修改,并且在你的函数完成时不会消失,这就是你得到{{ {1}}。

内部(let ((n 1)) (lambda (x) (set! n ...构造的函数捕获外部lambda的使用,并保持其位置,只要它本身存在。它还封装了n,因为没有其他任何东西可以引用它,但这个函数。我们还说该函数由n的周围绑定关闭,并且该函数是一个闭包(n)。

阅读词汇范围也可能对你有帮助。