stringtranslate.com

Desechar el patrón

En programación orientada a objetos , el patrón de eliminación es un patrón de diseño para la gestión de recursos . En este patrón, un objeto retiene un recurso y lo libera llamando a un método convencional (generalmente llamado ,,,, según el idioma) que libera cualquier recurso que el objeto esté reteniendo. Muchos lenguajes de programación ofrecen construcciones de lenguaje para evitar tener que llamar explícitamente al método de disposición en situaciones comunes.closedisposefreerelease

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

Motivación

Envolver recursos en objetos

Envolver recursos en objetos es la forma de encapsulación orientada a objetos y subyace al patrón de eliminación.

Los recursos suelen estar representados por identificadores (referencias abstractas), concretamente normalmente números enteros, que se utilizan para comunicarse con un sistema externo que proporciona el recurso. Por ejemplo, los archivos los proporciona el sistema operativo (específicamente el sistema de archivos ), que en muchos sistemas representa archivos abiertos con un descriptor de archivo (un número entero que representa el archivo).

Estos identificadores se pueden usar directamente, almacenando el valor en una variable y pasándolo como argumento a funciones que usan 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 información oculta y el usuario se abstrae de la representación real.

Por ejemplo, en la entrada/salida de archivos C , los archivos están representados por objetos del FILEtipo (llamados confusamente " 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 la secuencia. Estos objetos se crean llamando fopen(en términos orientados a objetos, un constructor ), que adquiere el recurso y le devuelve un puntero; el recurso se libera llamando fclosea un puntero al FILEobjeto. [1] En código:

ARCHIVO * f = fopen ( nombre de archivo , modo ); // Haz algo con f. cerrar ( f );    

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

f  =  abrir ( nombre de archivo ) # Hacer algo con f. F.cerca ()

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: adquiriéndolos en un constructor o fábrica y liberándolos mediante un método closeo explícito dispose.

liberación inmediata

El problema fundamental que pretende resolver 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, deben liberarse lo antes posible. Además, a veces es necesario realizar algún trabajo de finalización, especialmente para E/S, como vaciar los buffers para garantizar que todos los datos se escriban realmente.

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 se deben administrar los recursos (particularmente para programas de larga duración, programas que utilizan muchos recursos o por seguridad, para garantizar que los datos se escriban). La eliminación explícita significa que la finalización y liberación de recursos es determinista y rápida: el disposemétodo no se completa hasta que se realizan.

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 La 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 fse sale del alcance de la variable, el objeto de archivo al que fhace referencia se destruye y, como parte de esto, se libera el recurso.

RAII se basa en que la vida útil del objeto sea determinista; sin embargo, con la gestión automática de la memoria, la vida útil 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, sino cuando se abstraen. De hecho, la vida útil a menudo no es determinista, aunque puede serlo, especialmente 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 finaliza, es posible que no finalice los objetos y, en cambio, simplemente permita que el sistema operativo recupere memoria; si se requiere la finalización (por ejemplo, para vaciar los buffers), puede ocurrir pérdida de datos.

Por lo tanto, al no acoplar la administración de recursos a la vida útil del objeto, el patrón de eliminación permite que los recursos se liberen rápidamente, al tiempo que brinda flexibilidad de implementación para la administración de la memoria. El coste de esto es que los recursos deben gestionarse manualmente, lo que puede resultar tedioso y propenso a errores.

salida anticipada

Un problema clave con el patrón de eliminación es que si disposeno se llama al método, se filtra 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 de archivo ):  f  =  abrir ( nombre de archivo )  si  a :  devolver  x  f . cerrar ()  devolver  y

Si la función regresa en la primera devolución, el archivo nunca se cierra y el recurso se filtra.

def  func ( nombre de archivo ):  f  =  open ( nombre de archivo )  g ( f )  # Haga algo con f que pueda generar una excepción.  F.cerca ()

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

Ambos pueden ser manejados por una try...finallyconstrucción, que asegura que la cláusula finalmente siempre se ejecute al salir:

def  func ( nombre de archivo ):  intente :  f  =  abrir ( nombre de archivo )  # Haz algo.  finalmente :  f . cerca ()

Más genéricamente:

Recurso recurso = getResource (); try { // El recurso ha sido adquirido; realizar acciones con el recurso. ... } finalmente { // Liberar recurso, incluso si se lanzó una excepción. recurso . disponer (); }          

La try...finallyconstrucción es necesaria para una seguridad de excepción adecuada , 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 provoca un aumento del tamaño del código y, de no hacerlo, se producirá una pérdida de recursos en el programa.

Construcciones del lenguaje

Para que el uso seguro del patrón de disposición sea menos detallado, varios lenguajes tienen algún tipo de soporte integrado para recursos retenidos 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 ()) { // Realiza acciones con el recurso. ... }      

que es igual a:

Recurso recurso = GetResource () try { // Realiza acciones con el recurso. ... } finalmente { // Es posible que el recurso no se haya adquirido o ya se haya liberado si ( recurso ! = nulo ) (( IDisposable ) recurso ). Disponer (); }               

De manera similar, el lenguaje Python tiene una withdeclaración que se puede usar con un efecto similar con un objeto administrador de contexto . El protocolo del administrador de contexto requiere implementación __enter__y __exit__métodos que la construcción de declaración llama automáticamente withpara evitar la duplicación de código que de otro modo ocurriría con el patrón try/ . [3]finally

con  recurso_context_manager ()  como  recurso :  # Realizar acciones con el recurso.  ... # Realizar otras acciones donde se garantiza que el recurso será desasignado. ...

El lenguaje Javatry introdujo una nueva sintaxis llamada -with-resources en la versión 7 de Java. [4] Puede usarse 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 la excepción             // El recurso x se cierra automáticamente } // prueba 

Problemas

Más allá del problema clave de la gestión correcta de recursos en presencia de devoluciones y excepciones, y la gestión de recursos basada en montón (eliminación de objetos en un ámbito diferente al de donde se crearon), existen muchas complejidades adicionales asociadas con el patrón de eliminación. RAII evita en gran medida estos problemas . Sin embargo, en el uso común y sencillo no surgen estas complejidades: adquirir un único 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 elimina, pero el objeto todavía está activo en este punto), por lo que es posible que el recurso no esté disponible cuando el objeto intenta Úselo, por ejemplo, intentando leer desde un archivo cerrado. Esto significa que todos los métodos del objeto que utilizan el recurso potencialmente fallan, concretamente generalmente devolviendo un error o generando una excepción. En la práctica, esto es menor, ya que el uso de recursos generalmente también puede fallar por otras razones (por ejemplo, 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 otra posible falla. . 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 usan el recurso), generando una excepción (como ObjectDisposedExceptionen .NET ) si el objeto ha sido eliminado. [5]

Además, es posible invocar disposeun 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 es preferible que disposesea idempotente (lo que significa que "llamar varias veces es lo mismo que llamar una vez"). [5] Esto se implementa fácilmente usando el mismo disposedcampo booleano y comprobándolo en una cláusula de protección al comienzo de dispose, en ese caso regresando inmediatamente, en lugar de generar una excepción. [5] Java distingue los tipos desechables (aquellos que implementan AutoCloseable) de los tipos desechables donde disponer es idempotente (el subtipo Closeable).

La eliminación en presencia de herencia y la composición de objetos que contienen recursos tienen problemas análogos a la destrucción/finalización (a través de destructores o finalizadores). Además, dado que el patrón de eliminación generalmente no admite idiomas para esto, es necesario un código repetitivo . En primer lugar, si una clase derivada anula un disposemétodo de la clase base, el método anulado de la clase derivada generalmente necesita llamar al disposemétodo de la clase base para liberar adecuadamente los recursos contenidos en la base. En segundo lugar, si un objeto tiene una relación de "tiene" 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 ser desechable el objeto que lo 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 simplemente 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 dispone de ellos (análogo a destruir o finalizar los objetos propios).

La composición (poseer) proporciona encapsulación (sólo es necesario rastrear el objeto que se utiliza), pero a costa de una complejidad considerable cuando hay más relaciones entre objetos, mientras que la agregación (visualización) es considerablemente más simple, a costa de carecer 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 gestión de recursos para obtener detalles y más ejemplos.

Ver también

Notas

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

Referencias

  1. ^ stdio.h  - Referencia de definiciones básicas, especificación única de UNIX , versión 4 de The Open Group
  2. ^ Microsoft MSDN: uso de la declaración (referencia de C#)
  3. ^ Guido van Rossum , Nick Coghlan (13 de junio de 2011). "PEP 343: La" con "Declaración". Fundación de software Python.
  4. ^ Tutorial de Oracle Java: la declaración de prueba con recursos
  5. ^ abc "Patrón de eliminación".
  6. ^ "Interfaz desechable" . Consultado el 3 de abril de 2016 .

Otras lecturas