2009-08-20 9 views
52

Como desarrollador de OO, tal vez tenga dificultades para ver su valor. ¿Qué valor agregado dan? ¿Encajan en un mundo OO?Cierres: ¿por qué son tan útiles?

+2

Vea también http://stackoverflow.com/questions/256625/when-to-use-closure – Brian

Respuesta

28

Los cierres no le dan ningún poder extra.

Cualquier cosa que pueda lograr con ellos puede lograr sin ellos.

Pero son muy útiles para hacer que el código sea más claro y legible. Y como todos sabemos, el código abreviado legible es un código que es más fácil de depurar y contiene menos errores.

Déjeme darle ejemplo corto de Java del posible uso:

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

sería reemplazado (si Java tenía cierres) con:

button.addActionListener({ System.out.println("Pressed"); }); 
+10

No le dan ningún poder extra, pero le dan un estilo extra ... Sé que lo haría más bien escriba y lea su segundo ejemplo que el anterior. Si va un paso más allá y considera elementos como los métodos map(), filter() y fold() en languageslike scala versus sus alternativas java y el estilo se vuelve obvio – Martin

+0

Para que su ejemplo de código sea realmente correcto, NewActionListener debería ser una clase definida por el usuario también. Para emular los cierres, generalmente debe definir una nueva clase en el momento, solo para capturar las variables que necesita. – jalf

+17

Si el estilo/legibilidad no es potencia, entonces podríamos escribir cualquier aplicación en el mundo con tarjetas perforadas. –

5

mi humilde opinión, se trata de ser capaz de capturar bloques del código y contexto a ser referenciado en algún momento posterior y ejecutado cuando/if/según sea necesario.

Puede que no parezcan ser un gran problema, y ​​los cierres definitivamente no son algo que necesite para hacer las cosas a diario, pero puede hacer código mucho más simple y más limpio para escribir/administrar.

[editar - ejemplo de código basado en el comentario anterior]

Java:

List<Integer> numbers = ...; 
List<Integer> positives = new LinkedList<Integer>(); 
for (Integer number : integers) { 
    if (number >= 0) { 
     positives.add(number); 
    } 
} 

Scala (se pierden algunos de los demás detalles como la inferencia de tipos y comodines por lo que sólo estamos comparando el efecto de el cierre):

val numbers:List[Int] = ... 
val positives:List[Int] = numbers.filter(i:Int => i >= 0) 
+0

La captura de contexto (en una forma de lectura y escritura) es la razón por la que no me gustan los cierres (en lugar de las funciones como primero objetos de clase): aumenta los lugares donde puede tener cambios de estado inesperados. – kdgregory

+0

estuvo de acuerdo, pero con gran poder tiene una gran responsabilidad ... Si combina los cierres con tanta inmutabilidad como sea posible, se ahorrará gran parte de ese dolor, pero se necesita un poco de disciplina (pero entonces no abusa de prácticamente todos los aspectos). de C, así que no es imposible:]) – Martin

+0

Creo que es por eso que los cierres son tan populares en los lenguajes funcionales: la estructura libre de efectos secundarios del lenguaje evita el código estúpido. Y en JavaScript, las reglas que rigen el contexto adjunto son tan arcanas que la mayoría de la gente solo pretende que no existe. – kdgregory

3

Aquí están algunos artículos interesantes:

+0

El primer enlace se ha podrido. –

62

Se puede ver como una generalización de una clase.

Su clase tiene algo de estado. Tiene algunas variables miembro que sus métodos pueden usar.

Un cierre es simplemente una forma más conveniente de dar a una función acceso al estado local.

En lugar de tener que crear una clase que conozca la variable local que desea que utilice la función, puede simplemente definir la función in situ y acceder implícitamente a todas las variables que están actualmente visibles.

Cuando define un método miembro en un lenguaje OOP tradicional, su cierre es "todos los miembros visibles en esta clase".

Los lenguajes con soporte de cierre "adecuado" simplemente generalizan esto, por lo que el cierre de una función es "todas las variables visibles aquí". Si "aquí" es una clase, entonces tiene un método de clase tradicional.

Si "aquí" está dentro de otra función, entonces usted tiene lo que los programadores funcionales consideran un cierre. Su función ahora puede acceder a cualquier elemento visible en la función principal.

Así que es solo una generalización, eliminando la absurda restricción de que "las funciones solo se pueden definir dentro de las clases", pero manteniendo la idea de que "las funciones pueden ver las variables visibles en el punto donde están declaradas".

+0

Creo que es al revés: una clase es una generalización de un cierre. Un cierre es solo una función que tiene acceso a un grupo de estados, pero una clase tiene muchos métodos que comparten el acceso al mismo estado. Muchos idiomas (por ejemplo, Java, Python, etc.) tienen clases locales, que pueden capturar variables del ámbito circundante como un cierre; entonces son estrictamente más generales que cierres. – newacct

+2

@newacct: en absoluto. Puede crear fácilmente múltiples funciones con el mismo cierre. Simplemente créelos en el mismo lugar y capturarán las mismas variables. Pero como dije, un cierre captura "cualquier cosa visible aquí", mientras que un método de clase captura "todo lo que es visible aquí * y que está en la clase *. Por supuesto, no estoy hablando de clases locales, lo que realmente no hace ' t tiene mucho que ver con las clases "normales" – jalf

+0

@jalf so. ¿El método es un cierre? – hqt

3

Los cierres encajan bastante bien en un mundo OO.

Como ejemplo, considere C# 3.0: tiene cierres y muchos otros aspectos funcionales, pero sigue siendo un lenguaje muy orientado a objetos.

En mi experiencia, los aspectos funcionales de C# tienden a para permanecer dentro de la implementación de los miembros de la clase, y no tanto como parte de la API pública que mis objetos terminan exponiendo.

Como tal, el uso de cierres tiende a ser detalles de implementación en otro código orientado a objetos.

los uso todo el tiempo, ya que este fragmento de código de una de nuestras pruebas unitarias (contra Moq) muestra:

var typeName = configuration.GetType().AssemblyQualifiedName; 

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

Hubiera sido bastante difícil de especificar el valor de entrada (typeName) como parte de la expectativa de simulacro si no hubiera sido por la característica de cierre de C#.

-2

Tal vez en el mundo de la programación compilada los beneficios de los cierres sean menos notorios. En JavaScript, los cierres son increíblemente poderosos. Esto es por dos razones:

1) JavaScript es un lenguaje interpretado, por lo que la eficiencia de la instrucción y la conservación del espacio de nombres son increíblemente importantes para una ejecución más rápida y sensible de programas grandes o que evalúan grandes cantidades de datos.

2) JavaScript es un lenguaje lambda. Esto significa que las funciones de JavaScript son objetos de primera clase que definen el alcance y el alcance de una función primaria accesible para objetos secundarios. Esto es importante ya que una variable puede declararse en una función principal, usarse en una función secundaria y retener el valor incluso después de que la función secundaria regrese. Eso significa que una variable puede reutilizar una variable muchas veces con un valor ya definido por la última iteración de esa función.

Como resultado, los cierres son increíblemente potentes y proporcionan una eficacia superior en JavaScript.

28

Para mí, el mayor beneficio de los cierres es cuando estás escribiendo un código que inicia una tarea, deja la tarea en ejecución y especifica lo que debería suceder cuando se realiza la tarea. En general, el código que se ejecuta al final de la tarea necesita acceder a los datos que están disponibles al principio, y los cierres lo hacen fácil.

Por ejemplo, un uso común en JavaScript es iniciar una solicitud HTTP. Quien lo esté comenzando probablemente quiera controlar lo que sucede cuando llega la respuesta.Así que va a hacer algo como esto:

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

Debido a los cierres de JavaScript, la variable "requestID" es capturado dentro de la función de éxito. Esto muestra cómo puede escribir las funciones de solicitud y respuesta en el mismo lugar y compartir variables entre ellas. Sin cierres, tendría que pasar in requestID como argumento, o crear un objeto que contenga requestID y la función.

+6

Muy buen ejemplo del uso de cierres en el mundo real –

+0

Lo siento, pero lo que veo aquí es un anti-patrón del mundo real. Definir una función dentro y un objeto dentro de una lista de parámetros de una invocación de función => legibilidad deficiente. Y cuando alguien finalmente se da cuenta de que tiene más sentido pasar requestID como parámetro, tendrá un buen error "a veces" que será difícil La función anónima tendrá una identificación incorrecta solo cuando la llamada ajax no haya regresado antes de la siguiente solicitud. Honestamente, la forma tradicional de hacerlo es mejor para la legibilidad y la legibilidad, y realmente me pregunto si el rendimiento es demasiado alto. bette r con un cierre. – nus

+0

Acepto que la definición de la función dentro de la lista de parámetros no es necesaria, así es como sucedió que escribí el ejemplo. Definirlo dentro de la otra función es el punto. Pasar el ID de solicitud como un parámetro a menudo no es una opción. Por ejemplo, si está utilizando una biblioteca de terceros para realizar la solicitud, no puede controlar cómo llama a su función de devolución de llamada. –

4

Es una lástima, la gente ya no aprende Smalltalk in edu; allí, los cierres se usan para estructuras de control, devoluciones de llamadas, enumeración de colecciones, manejo de excepciones y más. Para un pequeño ejemplo, aquí es un hilo gestor de acción cola de trabajador (en Smalltalk):

|actionQueue| 

actionQueue := SharedQueue new. 
[ 
    [ 
     |a| 

     a := actionQueue next. 
     a value. 
    ] loop 
] fork. 

actionQueue add: [ Stdout show: 1000 factorial ]. 

y, para aquellos que no pueden leer Smalltalk, el mismo en JavaScript sintaxis:

var actionQueue; 

actionQueue = new SharedQueue; 
function() { 
    for (;;) { 
     var a; 

     a = actionQueue.next(); 
     a(); 
    }; 
}.fork(); 

actionQueue.add(function() { Stdout.show(1000.factorial()); }); 

(así, como se ve: la sintaxis de ayuda en la lectura del código)

Editar: observe cómo se hace referencia a actionQueue desde el interior de los bloques, que funciona incluso para el hilo de bloque en forma de horquilla. Eso es lo que hace que los cierres sean tan fáciles de usar.

0

En cuanto a la consulta final: "¿Los cierres encajan en un mundo OO?"

En muchos idiomas, los cierres, junto con las funciones de primera clase, proporcionan la base para el diseño de programas OO: me vienen a la mente Javascript, Lua y Perl.

En otro lenguaje orientado a objetos más "tradicional" con la que estoy familiarizado, Java, hay 2 diferencias principales:

  1. Las funciones se no primera clase constructos
  2. En Java los detalles de implementación de OO (si los cierres se usan internamente) no se expone directamente al desarrollador como en Javascript, Lua y Perl

Por lo tanto, en un idioma "OO tradicional" como Java, la adición de los cierres son, en gran medida, tanto azúcar sintáctico.

-2

mirando ejemplos anteriores, puedo agregar mi bit.

  • aprecio el estilo, parcialmente escondido estado y el encadenamiento de expresión, pero esto no es suficiente

  • Mucho puede decirse sobre el código simple y explícita

  • siento que Scala, Perl y algunos otros lenguajes tipo Scheme están diseñados para hacer una especie de taquigrafía para la forma particular de pensar del diseñador del lenguaje o hacerlos "más productivos"

Me gustaría tomar el simp licity of python y sintaxis temprana de java, etc. como una mejor forma de simplicidad y explicitud

Puede ser bastante rápido escribir cierres encadenados crípticos en un espacio reducido.sin embargo, todo se puede hacer mediante objetos simples y algoritmos

Se necesitan cierres, pero no tan a menudo para garantizar que sean ciudadanos de primera clase.

+0

De hecho. Puede escribir un sistema de objetos con recolección de basura en c. c es solo una pequeña abstracción del ensamblador. Pero, ¿qué tan fácil es escribir y mantener? Las abstracciones son útiles. Se dice que las abstracciones que están diseñadas en un idioma * están * reificadas *. La pregunta de "¿se necesitan cierres?" es en mi humilde opinión, la pregunta equivocada. Creo que la pregunta debería ser: "¿se puede escribir un código expresivo, más corto y/o más claro con cierres?" –

+0

Cuando veo algo así como: "c es solo una pequeña abstracción del ensamblador", estoy seguro de que estoy escuchando a alguien que nunca ha tocado el ensamblador. Son 2 idiomas MUY diferentes. –

Cuestiones relacionadas