Pregunta

¿Qué es esto " Ejecutar alrededor " idioma (o similar) que he estado escuchando? ¿Por qué podría usarlo y por qué no quiero usarlo?

¿Fue útil?

Solución

Básicamente es el patrón donde escribes un método para hacer cosas que siempre se requieren, p. asignación de recursos y limpieza, y hacer que la persona que llama pase "qué queremos hacer con el recurso". Por ejemplo:

public interface InputStreamAction
{
    void useStream(InputStream stream) throws IOException;
}

// Somewhere else    

public void executeWithFile(String filename, InputStreamAction action)
    throws IOException
{
    InputStream stream = new FileInputStream(filename);
    try {
        action.useStream(stream);
    } finally {
        stream.close();
    }
}

// Calling it
executeWithFile("filename.txt", new InputStreamAction()
{
    public void useStream(InputStream stream) throws IOException
    {
        // Code to use the stream goes here
    }
});

// Calling it with Java 8 Lambda Expression:
executeWithFile("filename.txt", s -> System.out.println(s.read()));

// Or with Java 8 Method reference:
executeWithFile("filename.txt", ClassName::methodName);

El código de llamada no tiene que preocuparse por el lado de apertura / limpieza: executeWithFile se ocupará de él.

Esto fue francamente doloroso en Java porque los cierres eran muy profundos, comenzando con Java 8, las expresiones lambda se pueden implementar como en muchos otros lenguajes (por ejemplo, expresiones lambda C # o Groovy), y este caso especial se maneja desde Java 7 con < code> try-with-resources y AutoClosable transmisiones.

Aunque " asignar y limpiar " es el ejemplo típico dado, hay muchos otros ejemplos posibles: manejo de transacciones, registro, ejecución de código con más privilegios, etc. Es básicamente un poco como el patrón de método de plantilla pero sin herencia.

Otros consejos

El idioma Execute Around se usa cuando te encuentras teniendo que hacer algo como esto:

//... chunk of init/preparation code ...
task A
//... chunk of cleanup/finishing code ...

//... chunk of identical init/preparation code ...
task B
//... chunk of identical cleanup/finishing code ...

//... chunk of identical init/preparation code ...
task C
//... chunk of identical cleanup/finishing code ...

//... and so on.

Para evitar repetir todo este código redundante que siempre se ejecuta " alrededor de " tus tareas reales, crearías una clase que se ocupará de ella automáticamente:

//pseudo-code:
class DoTask()
{
    do(task T)
    {
        // .. chunk of prep code
        // execute task T
        // .. chunk of cleanup code
    }
};

DoTask.do(task A)
DoTask.do(task B)
DoTask.do(task C)

Este modismo mueve todo el código redundante complicado en un solo lugar, y deja su programa principal mucho más legible (¡y mantenible!)

Eche un vistazo a esta publicación para ver un ejemplo de C # y este artículo para un ejemplo de C ++.

Un Execute Around Method es donde pasa código arbitrario a un método, que puede realizar la configuración y / o desmonte el código y ejecute su código en el medio.

Java no es el lenguaje en el que elegiría hacer esto. Es más elegante pasar un cierre (o expresión lambda) como argumento. Aunque los objetos son posiblemente equivalentes a cierres .

Me parece que el Método Execute Around es algo así como Inversión de control (Dependencia Inyección) que puede variar ad hoc, cada vez que llame al método.

Pero también podría interpretarse como un ejemplo de Control Coupling (que le dice a un método qué hacer por su argumento, literalmente en este caso).

Veo que tiene una etiqueta Java aquí, así que usaré Java como ejemplo, aunque el patrón no sea específico de la plataforma.

La idea es que a veces tiene un código que siempre involucra la misma plantilla antes de ejecutar el código y después de ejecutarlo. Un buen ejemplo es JDBC. Siempre toma una conexión y crea una declaración (o declaración preparada) antes de ejecutar la consulta real y procesar el conjunto de resultados, y luego siempre realiza la misma limpieza repetitiva al final, cerrando la declaración y la conexión.

La idea con execute-around es que es mejor si puede factorizar el código repetitivo. Eso te ahorra algo de tipeo, pero la razón es más profunda. Aquí es el principio de no repetir (DRY): aísla el código en una ubicación, por lo que si hay un error o necesita cambiarlo, o simplemente quiere entenderlo, todo está en un solo lugar.

Sin embargo, lo que es un poco complicado con este tipo de factorización es que tienes referencias que tanto el "antes" y '' después de '' partes necesitan ver. En el ejemplo de JDBC, esto incluiría la conexión y la declaración (preparada). Entonces, para manejar eso, esencialmente '' envuelve '' su código de destino con el código repetitivo.

Puede estar familiarizado con algunos casos comunes en Java. Uno es los filtros de servlet. Otro es AOP en torno a los consejos. Un tercero son las diversas clases xxxTemplate en Spring. En cada caso, tiene algún objeto contenedor en el que su "interesante" se inyecta el código (por ejemplo, la consulta JDBC y el procesamiento del conjunto de resultados). El objeto contenedor hace el " antes de " parte, invoca el código interesante y luego realiza el " después de " parte.

Consulte también Sandwiches de código , que examina esta construcción en muchos lenguajes de programación y ofrece algunas ideas interesantes de investigación & # 8217; y. Con respecto a la pregunta específica de por qué uno podría usarlo, el documento anterior ofrece algunos ejemplos concretos:

  

Tales situaciones surgen cada vez que un programa manipula recursos compartidos.   Las API para bloqueos, sockets, archivos o conexiones de bases de datos pueden requerir un   programa para cerrar o liberar explícitamente un recurso que anteriormente   adquirido. En un lenguaje sin recolección de basura, el programador es   responsable de asignar memoria antes de su uso y liberarla   despues de su uso. En general, una variedad de tareas de programación requieren un   programa para hacer un cambio, operar en el contexto de ese cambio, y   luego deshacer el cambio. Llamamos a estas situaciones sándwiches de código.

Y luego:

  

Los sándwiches de código aparecen en muchas situaciones de programación. Varios comunes   ejemplos relacionados con la adquisición y liberación de recursos escasos,   tales como bloqueos, descriptores de archivo o conexiones de socket. En mas   casos generales, cualquier cambio temporal del estado del programa puede requerir un   sándwich de código Por ejemplo, un programa basado en GUI puede ignorar temporalmente   entradas del usuario, o un núcleo del sistema operativo puede desactivar temporalmente el hardware   interrumpe Si no se restaura el estado anterior en estos casos, se provocará   errores graves.

El documento no explora por qué no usar este modismo, pero describe por qué es fácil equivocarse sin el nivel de lenguaje:

  

Los sándwiches de código defectuoso surgen con mayor frecuencia en presencia de   excepciones y su flujo de control invisible asociado. En efecto,   características especiales del lenguaje para administrar sándwiches de código surgen principalmente en   idiomas que admiten excepciones.

     

Sin embargo, las excepciones no son la única causa del código defectuoso   sándwiches Cada vez que se realizan cambios en el código body , nuevas rutas de control   pueden surgir que omiten el código after . En el caso más simple, un   el mantenedor solo necesita agregar una declaración return a un cuerpo de sándwich   Introducir un nuevo defecto, que puede conducir a errores silenciosos. Cuando el cuerpo   el código es grande y antes y después están ampliamente separados, tales errores   puede ser difícil de detectar visualmente.

Esto me recuerda el patrón de diseño de estrategia . Observe que el enlace al que apunté incluye código Java para el patrón.

Obviamente, uno podría realizar "Ejecutar alrededor" haciendo un código de inicialización y limpieza y simplemente pasando una estrategia, que siempre estará envuelta en un código de inicialización y limpieza.

Como con cualquier técnica utilizada para reducir la repetición de código, no debe usarlo hasta que tenga al menos 2 casos donde lo necesite, tal vez incluso 3 (como el principio YAGNI). Tenga en cuenta que la eliminación de la repetición del código reduce el mantenimiento (menos copias del código significa menos tiempo dedicado a copiar arreglos en cada copia), pero también aumenta el mantenimiento (más código total). Por lo tanto, el costo de este truco es que está agregando más código.

Este tipo de técnica es útil para algo más que la inicialización y la limpieza. También es bueno para cuando desee que sea más fácil llamar a sus funciones (por ejemplo, podría usarlo en un asistente para que los botones '' siguiente '' y '' anterior '' no necesiten declaraciones de casos gigantes para decidir qué hacer para ir a la página siguiente / anterior.

Trataré de explicar, como lo haría con un niño de cuatro años:

Ejemplo 1

Santa viene a la ciudad. Sus elfos codifican lo que quieran a sus espaldas y, a menos que cambien, las cosas se vuelven un poco repetitivas:

  1. Obtenga papel de regalo
  2. Obtenga Super Nintendo .
  3. Envuélvelo.

O esto:

  1. Obtenga papel de regalo
  2. Obtenga Muñeca Barbie .
  3. Envuélvelo.

.... ad nauseam un millón de veces con un millón de regalos diferentes: observe que lo único diferente es el paso 2. Si el paso dos es lo único que es diferente, entonces ¿por qué Santa duplica el código? ¿Está duplicando los pasos 1 y 3 un millón de veces? Un millón de regalos significa que está repitiendo innecesariamente los pasos 1 y 3 un millón de veces.

Ejecutar ayuda a resolver ese problema. y ayuda a eliminar el código. Los pasos 1 y 3 son básicamente constantes, permitiendo que el paso 2 sea la única parte que cambia.

Ejemplo # 2

Si aún no lo obtiene, aquí hay otro ejemplo: piense en un bocadillo que: el pan en el exterior siempre es el mismo, pero lo que está en el interior cambia según el tipo de arena que elija (por ejemplo, jamón). , queso, mermelada, mantequilla de maní, etc.). El pan siempre está en el exterior y no es necesario que lo repitas mil millones de veces por cada tipo de arena que estés creando.

Ahora, si lee las explicaciones anteriores, quizás le resulte más fácil de entender. Espero que esta explicación te haya ayudado.

Si quieres modismos geniales, aquí está:

//-- the target class
class Resource { 
    def open () { // sensitive operation }
    def close () { // sensitive operation }
    //-- target method
    def doWork() { println "working";} }

//-- the execute around code
def static use (closure) {
    def res = new Resource();
    try { 
        res.open();
        closure(res)
    } finally {
        res.close();
    }
}

//-- using the code
Resource.use { res -> res.doWork(); }
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top