java language-agnostic design-patterns idioms

java - ¿Qué es el modismo "Ejecutar alrededor"?



language-agnostic design-patterns (8)

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


Básicamente es el patrón en el que se escribe un método para hacer las cosas que siempre se requieren, por ejemplo, la asignación de recursos y la limpieza, y hacer que la persona que llama pase "lo que 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 necesita preocuparse por el lado de abrir / limpiar: lo executeWithFile .

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

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


Esto me recuerda el patrón de diseño de la estrategia . Tenga en cuenta que el enlace que señalé incluye código de Java para el patrón.

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

Al igual que con cualquier técnica utilizada para reducir la repetición del código, no debe usarlo hasta que tenga al menos 2 casos donde lo necesite, tal vez incluso 3 (al igual que el principio YAGNI). Tenga en cuenta que la eliminación de la repetición del código reduce el mantenimiento (menos copias de código significa menos tiempo de copia de correcciones 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 cuando desea facilitar la llamada de sus funciones (por ejemplo, puede 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.


La expresión Ejecutar Alrededor se usa cuando tienes 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 sus tareas reales, debería crear una clase que lo solucione 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!)

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


Si quieres modismos groovy, 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(); }


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

Ejemplo 1

Santa vendrá 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. Obtener papel de regalo
  2. Consigue Super Nintendo .
  3. Envolverlo.

O esto:

  1. Obtener papel de regalo
  2. Consigue Barbie Doll .
  3. Envolverlo.

.... 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 segundo paso es lo único que es diferente, ¿por qué Santa está duplicando el código, es decir, por qué está duplicando los pasos? 1 y 3 un millón de veces? Un millón de regalos significa que repite innecesariamente los pasos 1 y 3 un millón de veces.

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

Ejemplo n. ° 2

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

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


Un método de ejecución alrededor es donde pasa código arbitrario a un método, que puede realizar la configuración y / o el código de desmontaje y ejecutar el 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 una especie de Inversión de control (Inyección de dependencia) que puede variar ad hoc cada vez que llama al método.

Pero también podría interpretarse como un ejemplo de Control de acoplamiento (indicando a un método qué hacer con 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 tienes un código que siempre implica la misma repetición antes de ejecutar el código y después de ejecutar el código. Un buen ejemplo es JDBC. Siempre toma una conexión y crea una declaración (o una declaración preparada) antes de ejecutar la consulta real y procesar el conjunto de resultados, y luego siempre hace la misma limpieza repetitiva al final, cerrando la declaración y la conexión.

La idea de ejecutar es que es mejor si puedes 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, usted aisla el código en una ubicación, por lo que si hay un error o si necesita cambiarlo, o simplemente quiere entenderlo, todo está en un solo lugar.

Sin embargo, lo que es un poco complicado con este factor de factorización es que tienes referencias que las partes "antes" y "después" deben ver. En el ejemplo de JDBC, esto incluiría la conexión y el estado (preparado). Así que para manejar eso esencialmente "envuelve" su código objetivo con el código repetitivo.

Es posible que esté familiarizado con algunos casos comunes en Java. Uno es filtros de servlet. Otro es AOP en torno a consejos. Un tercero son las diversas clases xxxTemplate en Spring. En cada caso, tiene un objeto envoltorio en el que se inyecta su código "interesante" (es decir, la consulta JDBC y el procesamiento del conjunto de resultados). El objeto envoltorio hace la parte "antes", invoca el código interesante y luego hace la parte "después".


Ver también Code Sandwiches , que examina esta construcción en muchos lenguajes de programación y ofrece algunas ideas interesantes para la investigación. Con respecto a la pregunta específica de por qué uno podría usarla, 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 adquirió previamente. En un lenguaje sin recolección de basura, el programador es responsable de asignar memoria antes de su uso y liberarla después de su uso. En general, una variedad de tareas de programación requieren que un programa realice un cambio, opere en el contexto de ese cambio y luego deshaga el cambio. Llamamos a tales situaciones sándwiches de código.

Y después:

Los sándwiches de código aparecen en muchas situaciones de programación. Varios ejemplos comunes se relacionan con la adquisición y liberación de recursos escasos, como bloqueos, descriptores de archivos o conexiones de socket. En casos más 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 las entradas del usuario, o un kernel del sistema operativo puede deshabilitar temporalmente las interrupciones de hardware. Si no se restablece el estado anterior en estos casos, se producirán errores graves.

El documento no explora por qué no usar este modismo, pero sí describe por qué el modismo es fácil de equivocarse sin la ayuda del nivel de idioma:

Los sándwiches de códigos defectuosos surgen con mayor frecuencia en presencia de excepciones y su flujo de control invisible asociado. De hecho, las 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 de los sándwiches de códigos defectuosos. Cada vez que se realizan cambios en el código del cuerpo , pueden surgir nuevas rutas de control que eluden el código posterior . En el caso más simple, un mantenedor solo necesita agregar una declaración de return al cuerpo de un sándwich para introducir un nuevo defecto, que puede conducir a errores silenciosos. Cuando el código del cuerpo es grande y antes y después están ampliamente separados, tales errores pueden ser difíciles de detectar visualmente.