Закрытия: почему они так полезны? - PullRequest
55 голосов
/ 20 августа 2009

Как разработчик OO , возможно, мне трудно понять его значение. Какую добавленную стоимость они дают? Они вписываются в мир ОО?

Ответы [ 10 ]

66 голосов
/ 20 августа 2009

Вы можете видеть это как обобщение класса.

Ваш класс содержит какое-то состояние. У него есть некоторые переменные-члены, которые могут использовать его методы.

Закрытие - это просто более удобный способ дать функции доступ к локальному состоянию.

Вместо того, чтобы создавать класс, который знает о локальной переменной, которую вы хотите, чтобы функция использовала, вы можете просто определить функцию на месте, и она может неявно обращаться к каждой переменной, которая в данный момент видима.

Когда вы определяете метод-член на традиционном языке ООП, его закрытие - «все члены, видимые в этом классе».

Языки с «правильной» поддержкой замыкания просто обобщают это, поэтому замыкание функции - «все переменные, видимые здесь». Если « здесь » является классом, то у вас есть традиционный метод класса.

Если « здесь » находится внутри другой функции, то у вас есть то, что функциональные программисты считают замыканием. Теперь ваша функция может получить доступ ко всему, что было видно в родительской функции.

Так что это всего лишь обобщение, снятие глупого ограничения, что «функции могут быть определены только внутри классов», но сохранение идеи, что «функции могут видеть любые переменные, видимые в точке, где они объявлены».

29 голосов
/ 20 августа 2009

Закрытия не дают вам никакой дополнительной силы.

Все, что вы можете достичь с ними, вы можете достичь без них.

Но они очень удобны для того, чтобы сделать код более понятным и читабельным. И, как мы все знаем, чистый читаемый короткий код - это код, который легче отлаживать и содержит меньше ошибок.

Позвольте привести краткий пример возможного использования Java:

    button.addActionListener(new ActionListener() {
        @Override public void actionPerformed(ActionEvent e) {
            System.out.println("Pressed");
        }
    });

Будет заменено (если бы у Java были замыкания) на:

button.addActionListener( { System.out.println("Pressed"); } );
28 голосов
/ 10 октября 2009

Для меня самое большое преимущество замыканий - это когда вы пишете код, который запускает задачу, оставляет задачу для выполнения и указывает, что должно произойти, когда задача будет выполнена. Как правило, коду, выполняемому в конце задачи, требуется доступ к данным, доступным в начале, и замыкания облегчают это.

Например, обычное использование в JavaScript - запуск HTTP-запроса. Кто бы ни начал это, вероятно, хочет контролировать, что происходит, когда приходит ответ Итак, вы будете делать что-то вроде этого:

function sendRequest() {
  var requestID = "123";
  Ext.Ajax.request({
    url:'/myUrl'
    success: function(response) {
      alert("Request " + requestID + " returned");
    }
  });
}

Из-за замыканий JavaScript переменная requestID захватывается внутри функции успеха. Это показывает, как вы можете написать функции запроса и ответа в одном месте и обмениваться переменными между ними. Без замыканий вам потребуется передать requestID в качестве аргумента или создать объект, содержащий requestID и функцию.

5 голосов
/ 20 августа 2009

ИМХО, это сводится к возможности захвата блоков кода и их контекста, к которым можно будет обратиться в некоторый момент позже и выполнить, когда / если / как требуется.

Может показаться, что они не имеют большого значения, и замыкания определенно не являются чем-то необходимым для ежедневной работы - но они могут сделать код намного проще и понятнее для написания / управление.

[править - пример кода на основе комментария выше]

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)
4 голосов
/ 23 августа 2010

Жаль, люди больше не учат Smalltalk в edu; там замыкания используются для управляющих структур, обратных вызовов, перечисления коллекций, обработки исключений и многого другого. Для хорошего небольшого примера вот рабочий поток обработчика действий очереди (в 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 ссылаются изнутри блоков, что работает даже для разветвленного блока потока. Вот что делает крышки простыми в использовании.

3 голосов
/ 20 августа 2009

Затворы очень хорошо вписываются в мир OO.

В качестве примера рассмотрим C # 3.0: он имеет замыкания и множество других функциональных аспектов, но все еще является очень объектно-ориентированным языком.

По моему опыту, функциональные аспекты C # имеют тенденцию оставаться в рамках реализации членов класса, и не так сильно, как часть открытого API, который мои объекты в конечном итоге раскрывают.

Таким образом, использование замыканий, как правило, является деталями реализации в противном случае объектно-ориентированного кода.

Я использую их все время, так как этот фрагмент кода одного из наших модульных тестов (против Moq ) показывает:

var typeName = configuration.GetType().AssemblyQualifiedName;

var activationServiceMock = new Mock<ActivationService>();
activationServiceMock.Setup(s => s.CreateInstance<SerializableConfigurationSection>(typeName)).Returns(configuration).Verifiable();

Было бы довольно сложно указать входное значение (typeName) как часть ожидания Mock, если бы оно не было для функции закрытия C #.

3 голосов
/ 20 августа 2009

Вот несколько интересных статей:

0 голосов
/ 05 марта 2013

Глядя на приведенные выше примеры, я могу добавить свой бит.

  • Я ценю стиль, скрывающую состояние часть и цепочку выражения, но этого недостаточно

  • Многое можно сказать о простом и явном коде

  • Мне кажется, что Scala, Perl и некоторые другие языки типов Scheme предназначены для того, чтобы сделать своего рода сокращение для конкретного способа мышления разработчиком языка или сделать их "более продуктивными"

Я бы взял простоту Python и ранний синтаксис Java и т. Д. Как лучший способ простоты и ясности

Может быть довольно быстро писать загадочные замыкания в цепях в ограниченном пространстве. однако все можно сделать с помощью простых объектов и алгоритмов

закрытие необходимо, но не так часто, чтобы гарантировать, что он будет гражданином первого класса.

0 голосов
/ 07 ноября 2011

Что касается заключительного запроса: «Умещаются (замыкания) в мир ОО?»

Во многих языках замыкания, наряду с первоклассными функциями, обеспечивают основу для разработки ОО-программ: на ум приходят Javascript, Lua и Perl.

В другом, более "традиционном" языке ОО, с которым я знаком, в Java, есть 2 основных отличия:

  1. Функции не конструкции первого класса
  2. В Java детали реализации OO (используются ли замыкания внутри) не предоставляются непосредственно разработчику, как в Javascript, Lua и Perl

Поэтому в "традиционном ОО" языке, таком как Java, добавление замыканий в значительной степени является просто синтаксическим сахаром.

0 голосов
/ 20 августа 2009

Возможно, в мире скомпилированного программирования преимущества замыканий менее заметны. В JavaScript замыкания невероятно мощные. Это по двум причинам:

1) JavaScript - это интерпретируемый язык, поэтому эффективность инструкций и сохранение пространства имен невероятно важны для более быстрого и быстрого выполнения больших программ или программ, которые оценивают большие объемы ввода.

2) JavaScript - это лямбда-язык. Это означает, что в функциях JavaScript объекты первого класса, которые определяют область видимости и область видимости от родительской функции, доступны для дочерних объектов. Это важно, поскольку переменная может быть объявлена ​​в родительской функции, использоваться в дочерней функции и сохранять значение даже после возврата дочерней функции. Это означает, что переменная может многократно использоваться функцией со значением, уже определенным в последней итерации этой функции.

В результате замыкания являются невероятно мощными и обеспечивают превосходную эффективность в JavaScript.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...