Закрытие: почему они так полезны?
Как ОО разработчик, может быть, мне трудно видеть его значение. Какую добавленную стоимость они дают? Вписываются ли они в ОО-Мир?
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 ссылается изнутри блоков, что работает даже для разветвленного потока-блока. Вот что делает закрытие таким простым в использовании.
вот несколько интересных статей:
- пересечение границ: закрытие - Брюс Тейт, упоминает некоторые преимущества блокад
- определение замыкания - Нил Гафтер (один из людей, которые изобрели одно из предложений закрытия для Java)
закрытие довольно хорошо вписывается в мир 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 основных различия:
- функции не конструкции первого класса
- в Java реализация сведения о OO (используются ли замыкания внутри) не предоставляются непосредственно разработчику, как это происходит в Javascript, Lua и Perl
поэтому в" традиционном OO " языке, таком как Java, добавление замыканий в значительной степени просто так много синтаксического сахара.
возможно, в мире скомпилированного программирования преимущества замыканий менее заметны. В JavaScript замыкания невероятно мощные. Это происходит по двум причинам:
1) JavaScript является интерпретируемым языком, поэтому эффективность инструкций и сохранение пространства имен невероятно важны для более быстрого и более отзывчивого выполнения из больших программ или программ, которые оценивают большие объемы ввода.
2) JavaScript-это лямбда-язык. Это означает, что в Функции JavaScript-это объекты первого класса, которые определяют область, а область из родительской функции доступна для дочерних объектов. Это важно, так как переменная может быть объявлена в родительской функции, использоваться в дочерней функции и сохранять значение даже после возврата дочерней функции. Это означает, что переменная может быть повторно использована функцией много раз со значением, уже определенным последней итерацией этой функции.
в результате закрытия неимоверно сильны и обеспечивают главное эффективность в JavaScript.
глядя на приведенные выше примеры, я могу добавить свой бит.
Я ценю стиль, скрытие состояния и цепочку выражения, но этого недостаточно
много можно сказать о простом и явном коде
- Я чувствую, что Scala, Perl и некоторые другие языки типа схемы предназначены для того, чтобы сделать своего рода стенографию для конкретного способа мышления языковым дизайнером или сделать их " больше продуктивно"
Я бы взял простоту python и ранний синтаксис java и т. д. как лучший способ простоты и ясности
Это может быть довольно быстро, чтобы написать загадочные цепные замыкания в узком пространстве. однако все может быть сделано с помощью простых объектов и алгоритмов
закрытие необходимо, но не так часто, чтобы гарантировать, что они будут первоклассным гражданином .