Oop 闭包:为什么它们如此有用?

Oop 闭包:为什么它们如此有用?,oop,functional-programming,closures,Oop,Functional Programming,Closures,作为一名开发人员,也许我很难看到它的价值。它们能带来什么附加值?它们适合OO世界吗?闭包不会给您任何额外的功能 任何你能用它们实现的事情,没有它们你都能实现 但它们对于使代码更清晰易读非常有用。 我们都知道,干净易读的短代码是一种易于调试且包含较少bug的代码 让我给您举一个可能用法的简短Java示例: button.addActionListener(new ActionListener() { @Override public void actionPerformed

作为一名开发人员,也许我很难看到它的价值。它们能带来什么附加值?它们适合OO世界吗?

闭包不会给您任何额外的功能

任何你能用它们实现的事情,没有它们你都能实现

但它们对于使代码更清晰易读非常有用。 我们都知道,干净易读的短代码是一种易于调试且包含较少bug的代码

让我给您举一个可能用法的简短Java示例:

    button.addActionListener(new ActionListener() {
        @Override public void actionPerformed(ActionEvent e) {
            System.out.println("Pressed");
        }
    });
将替换为(如果Java有闭包):


IMHO归结为能够捕获代码块它们的上下文,以便稍后在某个点引用,并在需要时执行

它们可能看起来没什么大不了的,而且闭包肯定不是你每天都需要完成的事情——但是它们可以使代码编写/管理更加简单和干净

[编辑-基于上述评论的代码示例]

爪哇:


以下是一些有趣的文章:

  • -Bruce Tate,提到闭包的一些优点
  • -作者Neal Gafter(发明Java闭包方案之一的人之一)

    • 您可以将其视为类的泛化

      你的班级有一些状态。它的方法可以使用一些成员变量

      闭包只是一种更方便的方法,可以让函数访问本地状态

      您不必创建一个知道您希望函数使用的局部变量的类,只需当场定义函数,它就可以隐式访问当前可见的每个变量

      当您用传统的OOP语言定义一个成员方法时,它的闭包是“这个类中所有可见的成员”

      具有“适当”闭包支持的语言简单地概括了这一点,因此函数的闭包是“此处可见的所有变量”。如果“here”是一个类,那么您有一个传统的类方法

      如果“here”在另一个函数中,那么你就有了函数式程序员所认为的闭包。您的函数现在可以访问父函数中可见的任何内容


      因此,这只是一种泛化,消除了“函数只能在类中定义”的愚蠢限制,但保留了“函数可以看到在声明变量时可见的任何变量”的想法。

      闭包非常适合OO世界

      作为C 3的例子,它具有闭包和许多其他功能方面,但仍然是一种非常面向对象的语言。 根据我的经验,C#的功能方面往往停留在类成员的实现中,而不是我的对象最终公开的公共API的一部分

      因此,闭包的使用往往是面向对象代码中的实现细节

      我一直在使用它们,正如我们的一个单元测试(针对)的代码片段所示:

      var typeName=configuration.GetType().AssemblyQualifiedName;
      var activationServiceMock=new Mock();
      activationServiceMock.Setup(s=>s.CreateInstance(typeName)).Returns(configuration).Verifiable();
      

      如果没有C#的闭包特性,将输入值(typeName)指定为模拟期望的一部分将非常困难。

      也许在编译编程的世界中闭包的好处不那么明显。在JavaScript中,闭包的功能非常强大。这有两个原因:

      1) JavaScript是一种解释语言,因此指令效率和名称空间保护对于大型程序或评估大量输入的程序更快、响应更快地执行非常重要

      2) JavaScript是一种lambda语言。这意味着JavaScript函数是定义作用域的第一类对象,并且子对象可以访问父函数的作用域。这一点很重要,因为变量可以在父函数中声明、在子函数中使用,甚至在子函数返回后也可以保留值。这意味着一个变量可以被一个函数多次重用,其值在该函数的最后一次迭代中已经定义


      因此,闭包的功能非常强大,在JavaScript中提供了卓越的效率。

      对我来说,闭包的最大好处是在编写代码时启动任务,让任务继续运行,并指定任务完成时应该发生什么。通常,在任务结束时运行的代码需要访问开始时可用的数据,而闭包使这变得容易

      例如,JavaScript中的一个常见用法是启动HTTP请求。无论是谁启动它,都可能想控制响应到达时发生的事情。所以你可以这样做:

      function sendRequest() {
        var requestID = "123";
        $.ajax('/myUrl', {
          success: function(response) {
            alert("Request " + requestID + " returned");
          }
        });
      }
      

      由于JavaScript的闭包,“requestID”变量被捕获到success函数中。这显示了如何在同一位置编写请求和响应函数,并在它们之间共享变量。如果没有闭包,您需要将requestID作为参数传入,或者创建一个包含requestID和函数的对象。

      遗憾的是,人们不再在edu中学习Smalltalk;在那里,闭包用于控制结构、回调、集合枚举、异常处理等。 作为一个很好的小示例,下面是一个工作队列操作处理程序线程(在Smalltalk中):

      对于那些不能阅读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世界?”

      在许多语言中,闭包以及一流的功能,为设计面向对象程序提供了基础:JavaScript、Lua和PUR。

      val numbers:List[Int] = ...
      val positives:List[Int] = numbers.filter(i:Int => i >= 0)
      
      var typeName = configuration.GetType().AssemblyQualifiedName;
      
      var activationServiceMock = new Mock<ActivationService>();
      activationServiceMock.Setup(s => s.CreateInstance<SerializableConfigurationSection>(typeName)).Returns(configuration).Verifiable();
      
      function sendRequest() {
        var requestID = "123";
        $.ajax('/myUrl', {
          success: function(response) {
            alert("Request " + requestID + " returned");
          }
        });
      }
      
      |actionQueue|
      
      actionQueue := SharedQueue new.
      [
          [
              |a|
      
              a := actionQueue next.
              a value.
          ] loop
      ] fork.
      
      actionQueue add: [ Stdout show: 1000 factorial ].
      
      var actionQueue;
      
      actionQueue = new SharedQueue;
      function () {
          for (;;) {
              var a;
      
              a = actionQueue.next();
              a();
          };
      }.fork();
      
      actionQueue.add( function () { Stdout.show( 1000.factorial()); });