En una biblioteca en Java 7, tengo una clase que proporciona servicios a otras clases. Después de crear una instancia de esta clase de servicio, se puede llamar a un método varias veces (llamémoslo doWork()
método). Entonces no sé cuándo se completa el trabajo de la clase de servicio.
El problema es que la clase de servicio usa objetos pesados y debería liberarlos. Configuré esta parte en un método (llamémoslo release()
), pero no está garantizado que otros desarrolladores utilicen este método.
¿Hay alguna manera de obligar a otros desarrolladores a llamar a este método después de completar la tarea de clase de servicio? Por supuesto que puedo documentar eso, pero quiero forzarlos.
Nota: No puedo llamar al release()
método en el doWork()
método, porque doWork()
necesita esos objetos cuando se llama a continuación.
AutoCloseable
fue hecho para este propósito exacto.Respuestas:
La solución pragmática es hacer la clase
AutoCloseable
y proporcionar unfinalize()
método como respaldo (si es apropiado ... ¡vea a continuación!). Luego confía en los usuarios de su clase para usar try-with-resource o llamarclose()
explícitamente.Desafortunadamente, no hay forma 1 en Java para obligar al programador a hacer lo correcto. Lo mejor que puede hacer es recoger el uso incorrecto en un analizador de código estático.
Sobre el tema de finalizadores. Esta característica de Java tiene muy pocos casos de buen uso. Si confía en los finalizadores para ordenar, se encuentra con el problema de que puede tomar mucho tiempo para que suceda. El finalizador solo se ejecutará después de que el GC decida que ya no se puede alcanzar el objeto. Es posible que eso no suceda hasta que JVM realice una recopilación completa .
Por lo tanto, si el problema que está tratando de resolver es recuperar los recursos que deben liberarse antes de tiempo , entonces ha perdido el bote al usar la finalización.
Y en caso de que no haya entendido lo que digo anteriormente ... ¡ casi nunca es apropiado usar finalizadores en el código de producción, y nunca debe confiar en ellos!
1 - Hay formas. Si está preparado para "ocultar" los objetos de servicio del código de usuario o controlar estrictamente su ciclo de vida (por ejemplo, https://softwareengineering.stackexchange.com/a/345100/172 ), entonces el código de usuario no necesita llamar
release()
. Sin embargo, la API se vuelve más complicada, restringida ... e IMO "fea". Además, esto no obliga al programador a hacer lo correcto . ¡Está eliminando la capacidad del programador de hacer lo incorrecto!fuente
DefaultResource
yFinalizableResource
que extiende la primera y agrega un finalizador. En producción se usa elDefaultResource
que no tiene finalizador-> sin gastos generales. Durante el desarrollo y las pruebas, configura la aplicación para usarFinalizableResource
que tiene un finalizador y, por lo tanto, agrega sobrecarga. Durante el desarrollo y las pruebas, eso no es un problema, pero puede ayudarlo a identificar fugas y corregirlas antes de llegar a la producción. Sin embargo, si una fuga alcanza PRD, puede configurar la fábrica para crear X%FinalizableResource
para identificar la fugaEste parece ser el caso de uso de la
AutoCloseable
interfaz y latry-with-resources
declaración introducida en Java 7. Si tiene algo comoentonces sus consumidores pueden usarlo como
y no tener que preocuparse por llamar a un explícito
release
independientemente de si su código arroja una excepción.Respuesta adicional
Si lo que realmente está buscando es asegurarse de que nadie se olvide de usar su función, debe hacer un par de cosas
MyService
sean privados.MyService
que garantice que se limpie después.Harías algo como
Entonces lo usarías como
Originalmente no recomendé esta ruta porque es horrible sin las interfaces funcionales y lambdas de Java-8 (donde se
MyServiceConsumer
convierteConsumer<MyService>
) y es bastante imponente para sus consumidores. Pero si lo que solo quieres es querelease()
te llamen, funcionará.fuente
try-with-resources
y no solo omitirántry
, perdiendo así laclose
llamada?try-with-resources
?.close()
se llame cuando se implemente la claseAutoCloseable
.using
bloque para la finalización determinista? Al menos en C #, esto se convierte en un patrón bastante claro para que los desarrolladores tengan el hábito de usarlo cuando aprovechan los recursos que necesitan una finalización determinista. Algo fácil y adictivo es la siguiente mejor opción cuando no puedes obligar a alguien a hacer algo. stackoverflow.com/a/2016311/84206sí tu puedes
Puede obligarlos absolutamente a que se liberen, y hacer que sea 100% imposible de olvidar, haciendo imposible que creen nuevas
Service
instancias.Si hicieron algo como esto antes:
Luego cámbielo para que tengan acceso de esta manera.
Advertencias:
AutoCloseable
interfaz que alguien mencionó; pero el ejemplo dado debería funcionar fuera de la caja, y a excepción de la elegancia (que es un buen objetivo en sí mismo) no debería haber una razón importante para cambiarlo. Tenga en cuenta que esto tiene la intención de decir que puede usarAutoCloseable
dentro de su implementación privada; El beneficio de mi solución sobre el uso de sus usuariosAutoCloseable
es que, nuevamente, no se puede olvidar.new Service
llamada.Service
) pertenece o no a la persona que llama está fuera del alcance de esta respuesta. Pero si decides que lo necesitas absolutamente, así es como puedes hacerlo.fuente
Puede intentar utilizar el patrón de Comando .
Esto le brinda control total sobre la adquisición y liberación de recursos. La persona que llama solo envía tareas a su Servicio, y usted mismo es responsable de adquirir y limpiar recursos.
Sin embargo, hay una condición. La persona que llama aún puede hacer esto:
Pero puede abordar este problema cuando surja. Cuando haya problemas de rendimiento y descubra que algunas personas que llaman hacen esto, simplemente muéstreles la forma correcta y resuelva el problema:
Puede hacer esto arbitrariamente complejo implementando promesas para tareas que dependen de otras tareas, pero luego liberar cosas también se vuelve más complicado. Este es solo un punto de partida.
Asíncrono
Como se ha señalado en los comentarios, lo anterior realmente no funciona con solicitudes asincrónicas. Eso es correcto. Pero esto se puede resolver fácilmente en Java 8 con el uso de CompleteableFuture , especialmente
CompleteableFuture#supplyAsync
para crear los futuros individuales yCompleteableFuture#allOf
realizar la liberación de los recursos una vez que todas las tareas hayan finalizado. Alternativamente, uno siempre puede usar Threads o ExecutorServices para lanzar su propia implementación de Futures / Promises.fuente
Si puede, no permita que el usuario cree el recurso. Deje que el usuario pase un objeto visitante a su método, lo que creará el recurso, lo pasará al método del objeto visitante y lo liberará después.
fuente
AutoCloseable
hace algo muy similar detrás de escena. Bajo otro ángulo, es similar a la inyección de dependencia .Mi idea era similar a la de Polygnome.
Puede tener los métodos "do_something ()" en su clase simplemente agregando comandos a una lista privada de comandos. Luego, tenga un método "commit ()" que realmente haga el trabajo y llame a "release ()".
Entonces, si el usuario nunca llama a commit (), el trabajo nunca se realiza. Si lo hacen, los recursos se liberan.
Luego puede usarlo como foo.doSomething.doSomethineElse (). Commit ();
fuente
Otra alternativa a las mencionadas sería el uso de "referencias inteligentes" para administrar sus recursos encapsulados de una manera que permita al recolector de basura limpiar automáticamente sin liberar recursos manualmente. Su utilidad depende, por supuesto, de su implementación. Lea más sobre este tema en esta maravillosa publicación de blog: https://community.oracle.com/blogs/enicholas/2006/05/04/understanding-weak-references
fuente
Para cualquier otro lenguaje orientado a clases, diría que lo coloque en el destructor, pero como esa no es una opción, creo que puede anular el método de finalización. De esa manera, cuando la instancia se salga del alcance y se recolecte basura, se liberará.
No estoy muy familiarizado con Java, pero creo que este es el propósito para el cual finalize () está destinado.
http://docs.oracle.com/javase/7/docs/api/java/lang/Object.html#finalize ()
fuente
If you rely on finalizers to tidy up, you run into the problem that it can take a very long time for the tidy-up to happen. The finalizer will only be run after the GC decides that the object is no longer reachable. That may not happen until the JVM does a full collection.