Закрытие: почему они так полезны?


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

10 54

10 ответов:

закрытие не дает вам никакой дополнительной мощности.

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

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

позвольте мне дать вам короткий пример 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";
  Ext.Ajax.request({
    url:'/myUrl'
    success: function(response) {
      alert("Request " + requestID + " returned");
    }
  });
}

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

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

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

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

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)

жаль, что люди больше не изучают 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()); });

(ну, как видите: синтаксис помогает в чтении кода)

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

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

закрытие довольно хорошо вписывается в мир OO.

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

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

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

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

var typeName = configuration.GetType().AssemblyQualifiedName;

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

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

Что касается заключительного запроса: "подходят ли (замыкания) в мире OO?"

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

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

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

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

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

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

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

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

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

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

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

  • Я чувствую, что Scala, Perl и некоторые другие языки типа схемы предназначены для того, чтобы сделать своего рода стенографию для конкретного способа мышления языковым дизайнером или сделать их " больше продуктивно"

Я бы взял простоту python и ранний синтаксис java и т. д. как лучший способ простоты и ясности

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

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