stringtranslate.com

Patrón de descarte

En la programación orientada a objetos , el patrón dispose es un patrón de diseño para la gestión de recursos . En este patrón, un recurso es retenido por un objeto y liberado mediante una llamada a un método convencional (normalmente llamado close, dispose, free, releasesegún el lenguaje) que libera cualquier recurso que el objeto esté reteniendo. Muchos lenguajes de programación ofrecen construcciones de lenguaje para evitar tener que llamar al método dispose explícitamente en situaciones comunes.

El patrón dispose se utiliza principalmente en lenguajes cuyo entorno de ejecución tiene recolección automática de basura (ver la motivación a continuación).

Motivación

Envolviendo recursos en objetos

Envolver recursos en objetos es la forma de encapsulación orientada a objetos y es la base del patrón de disposición.

Los recursos se representan normalmente mediante identificadores (referencias abstractas), concretamente por lo general números enteros, que se utilizan para comunicarse con un sistema externo que proporciona el recurso. Por ejemplo, los archivos son proporcionados por el sistema operativo (en concreto, el sistema de archivos ), que en muchos sistemas representa los archivos abiertos con un descriptor de archivo (un número entero que representa el archivo).

Estos identificadores se pueden utilizar directamente, almacenando el valor en una variable y pasándolo como argumento a las funciones que utilizan el recurso. Sin embargo, con frecuencia es útil abstraerse del identificador en sí (por ejemplo, si diferentes sistemas operativos representan archivos de manera diferente) y almacenar datos auxiliares adicionales con el identificador, de modo que los identificadores se puedan almacenar como un campo en un registro , junto con otros datos; si se trata de un tipo de datos opaco , esto proporciona ocultamiento de información y el usuario se abstrae de la representación real.

Por ejemplo, en la entrada/salida de archivos de C , los archivos se representan mediante objetos del FILEtipo (confusamente llamados " identificadores de archivo ": son una abstracción a nivel de lenguaje), que almacena un identificador (del sistema operativo) para el archivo (como un descriptor de archivo ), junto con información auxiliar como el modo de E/S (lectura, escritura) y la posición en el flujo. Estos objetos se crean mediante una llamada fopen(en términos orientados a objetos, un constructor ), que adquiere el recurso y devuelve un puntero a él; el recurso se libera mediante una llamada fclosea un puntero al FILEobjeto. [1] En código:

ARCHIVO * f = fopen ( nombre_archivo , modo ); // Hacer algo con f. fclose ( f );    

Tenga en cuenta que fclosees una función con un FILE *parámetro. En la programación orientada a objetos, se trata de un método de instancia en un objeto de archivo, como en Python:

f  =  open ( nombre_archivo ) # Hacer algo con f. f . close ()

Este es precisamente el patrón de disposición, y solo difiere en la sintaxis y la estructura del código [a] de la apertura y cierre de archivos tradicionales. Otros recursos se pueden gestionar exactamente de la misma manera: se adquieren en un constructor o una fábrica y se liberan mediante un método closeo explícito.dispose

Liberación inmediata

El problema fundamental que se pretende resolver con la liberación de recursos es que los recursos son costosos (por ejemplo, puede haber un límite en la cantidad de archivos abiertos) y, por lo tanto, se deben liberar rápidamente. Además, a veces se necesita algún trabajo de finalización, en particular para la E/S, como vaciar los búferes para garantizar que se escriban todos los datos.

Si un recurso es ilimitado o efectivamente ilimitado, y no es necesaria una finalización explícita, no es importante liberarlo y, de hecho, los programas de corta duración a menudo no liberan recursos explícitamente: debido al corto tiempo de ejecución, es poco probable que agoten los recursos y dependen del sistema de ejecución o del sistema operativo para realizar cualquier finalización.

Sin embargo, en general, los recursos deben administrarse (en particular, en el caso de programas de larga duración, programas que utilizan muchos recursos o por razones de seguridad, para garantizar que se escriban los datos). La eliminación explícita significa que la finalización y liberación de los recursos es determinista y rápida: el disposemétodo no se completa hasta que se realizan estas tareas.

Una alternativa a exigir la eliminación explícita es vincular la gestión de recursos a la vida útil del objeto : los recursos se adquieren durante la creación del objeto y se liberan durante su destrucción . Este enfoque se conoce como el modismo Adquisición de recursos es inicialización (RAII) y se utiliza en lenguajes con gestión de memoria determinista (por ejemplo, C++ ). En este caso, en el ejemplo anterior, el recurso se adquiere cuando se crea el objeto de archivo y, cuando se sale del ámbito de la variable , se destruye fel objeto de archivo al que hace referencia y, como parte de esto, se libera el recurso.f

RAII se basa en que la duración de vida de los objetos sea determinista; sin embargo, con la gestión automática de la memoria, la duración de vida de los objetos no es una preocupación del programador: los objetos se destruyen en algún momento después de que ya no se utilizan, pero cuando se abstrae. De hecho, la duración de vida a menudo no es determinista, aunque puede serlo, en particular si se utiliza el recuento de referencias . De hecho, en algunos casos no hay garantía de que los objetos se finalicen alguna vez : cuando el programa termina, puede que no finalice los objetos y, en su lugar, simplemente deje que el sistema operativo recupere memoria; si se requiere la finalización (por ejemplo, para vaciar los búferes), puede producirse una pérdida de datos.

De este modo, al no vincular la gestión de recursos con la vida útil de los objetos, el patrón de eliminación permite liberar recursos rápidamente, al tiempo que brinda flexibilidad de implementación para la gestión de memoria. El costo de esto es que los recursos deben administrarse manualmente, lo que puede ser tedioso y propenso a errores.

Salida anticipada

Un problema clave con el patrón dispose es que si disposeno se llama al método, se pierde el recurso. Una causa común de esto es la salida anticipada de una función, debido a un retorno anticipado o una excepción.

Por ejemplo:

def  func ( nombre_archivo ) :  f  =  open ( nombre_archivo )  if  a :  return  x  f.close ( ) return y  

Si la función retorna en el primer retorno, el archivo nunca se cierra y se pierde el recurso.

def  func ( filename ):  f  =  open ( filename )  g ( f )  # Hacer algo con f que pueda generar una excepción.  f . close ()

Si el código intermedio genera una excepción, la función sale antes y el archivo nunca se cierra, por lo que se pierde el recurso.

Ambos pueden manejarse mediante una try...finallyconstrucción que garantiza que la cláusula finally siempre se ejecute al salir:

def  func ( nombre_archivo ):  try :  f  =  open ( nombre_archivo )  # Hacer algo.  finally :  f.close ( )

De manera más genérica:

Recurso resource = getResource (); try { // Se ha adquirido el recurso; realiza acciones con el recurso. ... } finally { // Libera el recurso, incluso si se lanzó una excepción. resource . dispose (); }          

La try...finallyconstrucción es necesaria para la seguridad adecuada de las excepciones , ya que el finallybloque permite la ejecución de la lógica de limpieza independientemente de si se lanza o no una excepción en el trybloque.

Una desventaja de este enfoque es que requiere que el programador agregue explícitamente código de limpieza en un finallybloque. Esto genera un aumento del tamaño del código y, si no se hace, se producirá una pérdida de recursos en el programa.

Construcciones del lenguaje

Para que el uso seguro del patrón dispose sea menos detallado, varios lenguajes tienen algún tipo de soporte incorporado para los recursos almacenados y liberados en el mismo bloque de código .

El lenguaje C# presenta la usingdeclaración [2] que llama automáticamente al Disposemétodo en un objeto que implementa la IDisposable interfaz :

usando ( Recurso recurso = GetResource ( )) { // Realizar acciones con el recurso. ... }      

que es igual a:

Recurso resource = GetResource () try { // Realizar acciones con el recurso. ... } finally { // Es posible que el recurso no se haya adquirido o que ya se haya liberado if ( resource != null ) (( IDisposable ) resource ). Dispose (); }               

De manera similar, el lenguaje Python tiene una withdeclaración que se puede utilizar con un efecto similar con un objeto de administrador de contexto . El protocolo de administrador de contexto requiere implementar métodos __enter__y __exit__que sean llamados automáticamente por la withconstrucción de la declaración, para evitar la duplicación de código que de otra manera ocurriría con el patrón try/ . [3]finally

con  resource_context_manager ()  como  recurso :  # Realizar acciones con el recurso.  ... # Realizar otras acciones en las que se garantiza que el recurso será desasignado. ...

El lenguaje Java introdujo una nueva sintaxis llamada try-with-resources en la versión 7 de Java. [4] Se puede utilizar en objetos que implementan la interfaz AutoCloseable (que define el método close()):

try ( OutputStream x = new OutputStream (...)) { // Hacer algo con x } catch ( IOException ex ) { // Manejar excepción             // El recurso x se cierra automáticamente } // intentar 

Problemas

Más allá del problema clave de la correcta gestión de recursos en presencia de devoluciones y excepciones, y la gestión de recursos basada en el montón (eliminar objetos en un ámbito diferente de donde se crean), existen muchas otras complejidades asociadas con el patrón dispose. Estos problemas se evitan en gran medida con RAII . Sin embargo, en el uso común y simple, estas complejidades no surgen: adquirir un solo recurso, hacer algo con él, liberarlo automáticamente.

Un problema fundamental es que tener un recurso ya no es una invariante de clase (el recurso se mantiene desde la creación del objeto hasta que se desecha, pero el objeto sigue estando activo en este punto), por lo que el recurso puede no estar disponible cuando el objeto intenta usarlo, por ejemplo, al intentar leer desde un archivo cerrado. Esto significa que todos los métodos del objeto que utilizan el recurso pueden fallar, concretamente, normalmente devolviendo un error o lanzando una excepción. En la práctica, esto es menor, ya que el uso de recursos normalmente también puede fallar por otras razones (por ejemplo, al intentar leer más allá del final de un archivo), por lo que estos métodos ya pueden fallar, y no tener un recurso solo agrega otro posible fallo. Una forma estándar de implementar esto es agregar un campo booleano al objeto, llamado disposed, que se establece en verdadero mediante dispose, y se verifica mediante una cláusula de protección para todos los métodos (que utilizan el recurso), lanzando una excepción (como ObjectDisposedExceptionen .NET) si el objeto ha sido desechado. [5]

Además, es posible llamar disposea un objeto más de una vez. Si bien esto puede indicar un error de programación (cada objeto que contiene un recurso debe eliminarse exactamente una vez), es más simple, más robusto y, por lo tanto, generalmente preferible que disposesea idempotente (lo que significa "llamar varias veces es lo mismo que llamar una vez"). [5] Esto se implementa fácilmente utilizando el mismo disposedcampo booleano y comprobándolo en una cláusula de protección al comienzo de dispose, en ese caso retornando inmediatamente, en lugar de lanzar una excepción. [5] Java distingue los tipos desechables (aquellos que implementan AutoCloseable) de los tipos desechables donde dispose es idempotente (el subtipo Closeable).

La eliminación en presencia de herencia y composición de objetos que contienen recursos tiene problemas análogos a la destrucción/finalización (a través de destructores o finalizadores). Además, dado que el patrón dispose generalmente no tiene soporte de lenguaje para esto, es necesario un código repetitivo . En primer lugar, si una clase derivada anula un disposemétodo en la clase base, el método anulador en la clase derivada generalmente necesita llamar al disposemétodo en la clase base, para liberar adecuadamente los recursos almacenados en la base. En segundo lugar, si un objeto tiene una relación "tiene un" con otro objeto que contiene un recurso (es decir, si un objeto usa indirectamente un recurso a través de otro objeto que usa directamente un recurso), ¿debería descartarse el objeto que usa indirectamente? Esto corresponde a si la relación es de propiedad ( composición de objetos ) o de visualización ( agregación de objetos ), o incluso solo de comunicación ( asociación ), y se encuentran ambas convenciones (el usuario indirecto es responsable del recurso o no es responsable). Si el uso indirecto es responsable del recurso, éste debe ser desechable, y disponer de los objetos propios cuando se desecha (de forma análoga a destruir o finalizar los objetos propios).

La composición (posesión) proporciona encapsulación (solo se debe rastrear el objeto que se usa), pero a costa de una complejidad considerable cuando hay más relaciones entre los objetos, mientras que la agregación (visualización) es considerablemente más simple, a costa de la falta de encapsulación. En .NET , la convención es que solo el usuario directo de los recursos sea responsable: "Debe implementar IDisposable solo si su tipo usa recursos no administrados directamente". [6] Consulte la administración de recursos para obtener detalles y más ejemplos.

Véase también

Notas

  1. ^ En la programación basada en clases , los métodos se definen en una clase, utilizando un parámetro implícito this, selfen lugar de como funciones que toman un parámetro explícito.

Referencias

  1. ^ stdio.h  – Referencia de definiciones básicas, La especificación única de UNIX , versión 4 de The Open Group
  2. ^ Microsoft MSDN: Declaración using (Referencia de C#)
  3. ^ Guido van Rossum , Nick Coghlan (13 de junio de 2011). "PEP 343: La declaración "con"". Python Software Foundation.
  4. ^ Tutorial de Oracle Java: La declaración try-with-resources
  5. ^ abc "Patrón de desecho".
  6. ^ "Interfaz IDisposable" . Consultado el 3 de abril de 2016 .

Lectura adicional