En programación informática , la inicialización diferida es la táctica de retrasar la creación de un objeto , el cálculo de un valor o algún otro proceso costoso hasta la primera vez que se lo necesita. Es un tipo de evaluación diferida que se refiere específicamente a la instanciación de objetos u otros recursos.
Esto se logra generalmente ampliando un método de acceso (o un captador de propiedades) para verificar si un miembro privado, que actúa como caché, ya se ha inicializado. Si es así, se devuelve de inmediato. Si no, se crea una nueva instancia, se coloca en la variable miembro y se devuelve al llamador justo a tiempo para su primer uso.
Si los objetos tienen propiedades que rara vez se utilizan, esto puede mejorar la velocidad de inicio. El rendimiento promedio del programa puede ser ligeramente peor en términos de memoria (para las variables de condición) y ciclos de ejecución (para verificarlas), pero el impacto de la instanciación de objetos se distribuye en el tiempo ("se amortiza") en lugar de concentrarse en la fase de inicio de un sistema y, por lo tanto, los tiempos de respuesta promedio pueden mejorarse en gran medida.
En el código multiproceso , el acceso a los objetos/estados inicializados de forma diferida debe estar sincronizado para protegerse contra condiciones de carrera .
En una perspectiva de patrón de diseño de software , la inicialización diferida se suele utilizar junto con un patrón de método de fábrica . Esto combina tres ideas:
El siguiente es un ejemplo de una clase con inicialización diferida implementada en ActionScript :
ejemplos de paquetes . lazyinstantiation { clase pública Fruit { var privada _typeName : String ; var estática privada instancesByTypeName : Dictionary = new Dictionary (); función pública Fruit ( typeName : String ) : void { this._typeName = typeName ; } función pública get typeName ( ) : String { return _typeName ; } función estática pública getFruitByTypeName ( typeName : String ) : Fruit { return instancesByTypeName [ typeName ] || = new Fruit ( typeName ); } función estática pública printCurrentTypes () : void { for each ( var fruit : Fruit in instancesByTypeName ) { // itera a través de cada valor trace ( fruit.typeName ) ; } } } }
Uso básico:
paquete { importar ejemplos . lazyinstantiation ; clase pública Main { función pública Main () : void { Fruta . getFruitByTypeName ( "Plátano" ); Fruta . printCurrentTypes (); Fruta . getFruitByTypeName ( "Manzana" ); Fruta . printCurrentTypes (); Fruta . getFruitByTypeName ( "Plátano" ); Fruta . printCurrentTypes (); } } }
En C , la evaluación perezosa normalmente se implementaría dentro de una función o un archivo fuente, utilizando variables estáticas .
En una función:
#include <string.h> #include <stdlib.h> #include <stddef.h> #include <stdio.h> struct fruit { char * nombre ; struct fruit * siguiente ; int número ; /* Otros miembros */ }; struct fruta * obtener_fruta ( char * nombre ) { static struct fruta * lista_frutas ; static int seq ; struct fruta * f ; for ( f = lista_frutas ; f ; f = f -> next ) if ( 0 == strcmp ( nombre , f -> nombre )) return f ; if ( ! ( f = malloc ( sizeof ( struct fruta )))) return NULL ; if ( ! ( f -> nombre = strdup ( nombre ))) { free ( f ); return NULL ; } f -> numero = ++ seq ; f -> next = lista_frutas ; lista_frutas = f ; return f ; } /* Código de ejemplo */int main ( int argc , char * argv []) { int i ; struct fruta * f ; if ( argc < 2 ) { fprintf ( stderr , "Uso: frutas nombre-de-fruta [...] \n " ); exit ( 1 ); } for ( i = 1 ; i < argc ; i ++ ) { if (( f = get_fruta ( argv [ i ]))) { printf ( "Fruta %s: número %d \n " , argv [ i ], f -> número ); } } return 0 ; }
En cambio, el uso de un solo archivo fuente permite compartir el estado entre múltiples funciones, al mismo tiempo que lo oculta de funciones no relacionadas.
fruta.h:
#ifndef _FRUTA_INCLUIDA_ #define _FRUTA_INCLUIDA_struct fruit { char * nombre ; struct fruit * siguiente ; int número ; /* Otros miembros */ }; struct fruta * obtener_fruta ( char * nombre ); void imprimir_lista_fruta ( ARCHIVO * archivo ); #endif /* _FRUTA_INCLUIDA_ */
fruta.c:
#include <string.h> #include <stdlib.h> #include <stddef.h> #include <stdio.h> #include "fruta.h" estructura estática fruit * fruit_list ; int estático seq ; struct fruta * obtener_fruta ( char * nombre ) { struct fruta * f ; para ( f = lista_frutas ; f ; f = f -> siguiente ) si ( 0 == strcmp ( nombre , f -> nombre )) devuelve f ; si ( ! ( f = malloc ( sizeof ( struct fruta )))) devuelve NULL ; si ( ! ( f -> nombre = strdup ( nombre ))) { libre ( f ); devuelve NULL ; } f -> numero = ++ seq ; f -> siguiente = lista_frutas ; lista_frutas = f ; devuelve f ; } void print_lista_frutas ( ARCHIVO * archivo ) { struct fruta * f ; for ( f = lista_frutas ; f ; f = f -> siguiente ) fprintf ( archivo , "%4d %s \n " , f -> número , f -> nombre ); }
principal.c:
#include <stdlib.h> #include <stdio.h> #include "fruta.h" int main ( int argc , char * argv []) { int i ; struct fruit * f ; if ( argc < 2 ) { fprintf ( stderr , "Uso: frutas nombre-de-la-fruta [...] \n " ); exit ( 1 ); } for ( i = 1 ; i < argc ; i ++ ) { if (( f = get_fruit ( argv [ i ]))) { printf ( "Fruta %s: número %d \n " , argv [ i ], f -> número ); } } printf ( "Se han generado las siguientes frutas: \n " ); print_fruit_list ( stdout ); return 0 ; }
En .NET Framework 4.0, Microsoft ha incluido una Lazy
clase que se puede utilizar para realizar una carga diferida. A continuación, se muestra un código ficticio que realiza una carga diferida de la claseFruit
var lazyFruit = new Lazy < Fruit > (); Fruta fruta = lazyFruit . Valor ;
A continuación se muestra un ejemplo ficticio en C# .
La Fruit
clase en sí no hace nada aquí, la variable de clase _typesDictionary
es un diccionario/mapa utilizado para almacenar Fruit
instancias por typeName
.
usando System ; usando System.Collections ; usando System.Collections.Generic ; clase pública Fruta { cadena privada _typeName ; IDictionary estático privado < cadena , Fruta > _typesDictionary = nuevo Diccionario < cadena , Fruta > (); privado Fruta ( string typeName ) { this._typeName = typeName ; } público estático Fruta GetFruitByTypeName ( cadena tipo ) { Fruta fruta ; if ( ! _typesDictionary . TryGetValue ( tipo , out fruta )) { // Inicialización perezosa fruta = new Fruta ( tipo ); _typesDictionary . Agregar ( tipo , fruta ); } devolver fruta ; } public static void ShowAll () { if ( _typesDictionary.Count > 0 ) { Console.WriteLine ( "Número de instancias creadas = {0}" , _typesDictionary.Count ) ; foreach ( KeyValuePair < string , Fruit > kvp in _typesDictionary ) { Console.WriteLine ( kvp.Key ) ; } Console.WriteLine ( ) ; } } public Fruit ( ) { // requerido para que la muestra se compile } } clase Programa { static void Main ( string [] args ) { Fruta . GetFruitByTypeName ( "Banana" ); Fruta . ShowAll (); Fruta . GetFruitByTypeName ( "Manzana" ); Fruta . ShowAll (); // devuelve la instancia preexistente de la primera // vez que se creó la Fruta con "Banana" Fruta . GetFruitByTypeName ( "Banana" ); Fruta . ShowAll (); Consola .ReadLine ( ); } }
Un ejemplo bastante sencillo de "rellenar los espacios en blanco" de un patrón de diseño de inicialización diferida, excepto que utiliza una enumeración para el tipo
espacio de nombres DesignPatterns.LazyInitialization ; clase pública LazyFactoryObject { // colección interna de elementos // IDictionary se asegura de que sean únicos private IDictionary < LazyObjectSize , LazyObject > _LazyObjectList = new Dictionary < LazyObjectSize , LazyObject > (); // enumeración para pasar el nombre del tamaño requerido // evita pasar cadenas y es parte de LazyObject antes public enum LazyObjectSize { None , Small , Big , Bigger , Huge } // tipo estándar de objeto que se construirá public struct LazyObject { public LazyObjectSize Size ; public IList < int > Result ; } // toma el tamaño y crea una lista 'cara' private IList < int > Result ( LazyObjectSize size ) { IList < int > result = null ; switch ( tamaño ) { caso LazyObjectSize . Small : resultado = CreateSomeExpensiveList ( 1 , 100 ); descanso ; caso LazyObjectSize . Big : resultado = CreateSomeExpensiveList ( 1 , 1000 ); descanso ; caso LazyObjectSize . Bigger : resultado = CreateSomeExpensiveList ( 1 , 10000 ) ; descanso ; caso LazyObjectSize . Huge : resultado = CreateSomeExpensiveList ( 1 , 100000 ); descanso ; caso LazyObjectSize . None : resultado = null ; descanso ; predeterminado : resultado = null ; descanso ; } devolver resultado ; } // no es un elemento costoso de crear, pero entiendes el punto // retrasa la creación de algún objeto costoso hasta que sea necesario private IList < int > CreateSomeExpensiveList ( int start , int end ) { IList < int > result = new List < int > (); para ( int contador = 0 ; contador < ( fin - inicio ); contador ++ ) { resultado . Agregar ( inicio + contador ); } devolver resultado ; } público LazyFactoryObject () { // constructor vacío } public LazyObject GetLazyFactoryObject ( LazyObjectSize size ) { // Sí, sé que es analfabeto e inexacto LazyObject noGoodSomeOne ; // recupera LazyObjectSize de la lista a través de out, de lo contrario crea uno y lo agrega a la lista if ( ! _LazyObjectList . TryGetValue ( size , out noGoodSomeOne )) { noGoodSomeOne = new LazyObject (); noGoodSomeOne . Size = size ; noGoodSomeOne . Result = this . Result ( size ); _LazyObjectList .Add ( tamaño , noGoodSomeOne ) ; } devuelve noGoodSomeOne ; } }
Este ejemplo está en C++ .
#include <iostream> #include <mapa> #include <cadena> clase Fruta { público : estático Fruta * GetFruit ( const std :: string & type ); estático void PrintCurrentTypes (); privado : // Nota: el constructor privado obliga a utilizar el método estático |GetFruit|. Fruit ( const std :: string & type ) : type_ ( type ) {} static std :: map < std :: string , Fruta *> tipos ; std :: tipo de cadena_ ; }; // static std :: map < std :: string , Fruta *> Fruta :: tipos ; // Método Lazy Factory, obtiene la instancia de |Fruit| asociada con un cierto // |type|. Crea nuevas según sea necesario. Fruit * Fruit::GetFruit ( const std :: string & type ) { auto [ it , insert ] = types.emplace ( type , nullptr ) ; if ( insert ) { it - > second = new Fruit ( type ); } return it -> second ; } // Para fines de ejemplo , ver el patrón en acción. void Fruit::PrintCurrentTypes () { std :: cout << "Número de instancias creadas = " << types.size ( ) << std :: endl ; for ( const auto & [ type , fruit ] : types ) { std :: cout << type << std :: endl ; } std :: cout << std :: endl ; } int main () { Fruta :: ObtenerFruta ( "Plátano" ); Fruta :: PrintCurrentTypes (); Fruta :: ObtenerFruit ( "Manzana" ); Fruta :: PrintCurrentTypes (); // Devuelve la instancia preexistente de la primera vez que se creó |Fruit| con "Banana". Fruit :: GetFruit ( "Banana" ); Fruit :: PrintCurrentTypes (); } // SALIDA: // // Número de instancias creadas = 1 // Banana // // Número de instancias creadas = 2 // Manzana // Banana // // Número de instancias creadas = 2 // Manzana // Banana //
clase Fruta tipo de captador privado : String @@types = {} de String => Fruta def inicializar ( @type ) fin def self . get_fruit_by_type ( tipo : String ) @@types [ tipo ] ||= Fruta . new ( tipo ) fin def self . show_all puts "Número de instancias creadas: #{ @@types . size } " @@types . each do | type , fruit | puts " #{ type } " end puts end def self . tamaño @@tipos . tamaño fin fin Fruta . get_fruit_by_type ( "Plátano" ) Fruta . show_allFruta . get_fruit_by_type ( "Manzana" ) Fruta . show_allFruta . get_fruit_by_type ( "Plátano" ) Fruta . show_all
Producción:
Número de instancias realizadas: 1BananaNúmero de instancias realizadas: 2BananaManzanaNúmero de instancias realizadas: 2BananaManzana
Este ejemplo está en Haxe . [1]
clase Fruta { var estática privada _instances = new Map < String , Fruta >(); nombre de var público ( predeterminado , nulo ): cadena ; función pública nueva ( nombre : String ) { this.nombre = nombre ; } función estática pública getFruitByName ( nombre : String ): Fruta { if ( ! _instances . exists ( nombre )) { _instances . set ( nombre , new Fruta ( nombre )); } return _instances . get ( nombre ); } función pública estática printAllTypes () { trace ([ for ( clave en _instances.keys ()) clave ] ) ; } }
Uso
clase Prueba { función pública estática main () { var banana = Fruit . getFruitByName ( "Banana" ); var apple = Fruit . getFruitByName ( "Apple" ); var banana2 = Fruit . getFruitByName ( "Banana" ); trace ( banana == banana2 ); // verdadero. mismo banana Fruit . printAllTypes (); // ["Banana", "Manzana"] } }
This example is not thread-safe, see the talk page. Instead see the examples in Double-checked locking#Usage in Java. |
Este ejemplo está en Java .
importar java.util.HashMap ; importar java.util.Map ; importar java.util.Map.Entry ; clase pública Programa { /** * @param args */ public static void main ( String [] args ) { Fruta . getFruitByTypeName ( FruitType . plátano ); Fruta . mostrar todo (); Fruta . getFruitByTypeName ( FruitType . manzana ); Fruta . mostrar todo (); Fruta . getFruitByTypeName ( FruitType . plátano ); Fruta . mostrar todo (); } } enum FruitType { ninguno , manzana , plátano , } clase Fruta { private static Map < FruitType , Fruit > types = new HashMap <> (); /** * Usando un constructor privado para forzar el uso del método de fábrica. * @param type */ private Fruit ( FruitType type ) { } /** * Método de fábrica perezoso, obtiene la instancia de Fruit asociada con un cierto * tipo. Crea instancias nuevas según sea necesario. * @param type Cualquier tipo de fruta permitido, p. ej. APPLE * @return La instancia de Fruit asociada con ese tipo. */ public static Fruit getFruitByTypeName ( FruitType type ) { Fruit fruit ; // Esto tiene problemas de concurrencia. Aquí la lectura de tipos no está sincronizada, // por lo que types.put y types.containsKey podrían llamarse al mismo tiempo. // No se sorprenda si los datos están dañados. if ( ! types . containsKey ( type )) { // Inicialización perezosa fruit = new Fruit ( type ); types . put ( type , fruit ); } else { // OK, está disponible actualmente fruit = types . get ( type ); } return fruit ; } /** * Método Lazy Factory, obtiene la instancia de Fruit asociada con un cierto * tipo. Crea instancias nuevas según sea necesario. Utiliza un patrón de bloqueo de doble verificación * para usar en entornos altamente concurrentes. * @param type Cualquier tipo de fruta permitido, p. ej. APPLE * @return La instancia de Fruit asociada con ese tipo. */ public static Fruit getFruitByTypeNameHighConcurrentVersion ( FruitType type ) { if ( ! types . containsKey ( type )) { synchronousd ( types ) { // Verifique nuevamente, después de haber adquirido el bloqueo para asegurarse // la instancia no fue creada mientras tanto por otro hilo if ( ! types . containsKey ( type )) { // Inicialización diferida types . put ( type , new Fruit ( type )); } } } return types . get ( type ); } /** * Muestra todas las frutas ingresadas. */ public static void showAll () { if ( types . size () > 0 ) { System . out . println ( "Número de instancias creadas = " + types . size ()); for ( Entry < FruitType , Fruit > entry : types . entrySet ()) { String fruit = entry . getKey (). toString (); fruit = Character . toUpperCase ( fruit . charAt ( 0 )) + fruit . substring ( 1 ); System . out . println ( fruit ); } System . out . println (); } } }
Producción
Número de instancias realizadas = 1BananaNúmero de instancias realizadas = 2BananaManzanaNúmero de instancias realizadas = 2BananaManzana
Este ejemplo está en JavaScript .
var Fruta = ( función () { var tipos = {}; función Fruta () {}; // contar propiedades propias en el objeto función count ( obj ) { return Object.keys ( obj ) .length ; } var _static = { getFruit : function ( tipo ) { if ( typeof tipos [ tipo ] == 'undefined' ) { tipos [ tipo ] = new Fruta ; } return tipos [ tipo ]; }, printCurrentTypes : function () { console.log ( 'Número de instancias creadas: ' + count ( tipos ) ) ; for ( var tipo in tipos ) { console.log ( tipo ) ; } } } ; devolver _static ; })();Fruta . obtenerFruta ( 'Manzana' ); Fruta . imprimirTiposActuales (); Fruta . obtenerFruta ( 'Plátano' ); Fruta . imprimirTiposActuales (); Fruta . obtenerFruta ( 'Manzana' ); Fruta . imprimirTiposActuales ();
Producción
Número de instancias realizadas: 1ManzanaNúmero de instancias realizadas: 2ManzanaBananaNúmero de instancias realizadas: 2ManzanaBanana
A continuación se muestra un ejemplo de inicialización diferida en PHP 7.4:
<?php header ( 'Tipo de contenido: texto/sin formato; conjunto de caracteres=utf-8' );clase Fruta { cadena privada $tipo ; matriz estática privada $tipos = matriz (); función privada __construct ( cadena $tipo ) { $this -> tipo = $tipo ; } función estática pública getFruit ( string $type ) { // La inicialización diferida tiene lugar aquí if ( ! isset ( self :: $types [ $type ])) { self :: $types [ $type ] = new Fruit ( $type ); } devuelve yo mismo :: $tipos [ $tipo ]; } función pública estática printCurrentTypes () : void { echo 'Número de instancias creadas: ' .count ( self :: $ types ) . " \n " ; foreach ( array_keys ( self :: $types ) como $key ) { echo " $key \n " ; } echo " \n " ; } } Fruta :: obtenerFruta ( 'Manzana' ); Fruta :: printCurrentTypes ();Fruta :: obtenerFruta ( 'Plátano' ); Fruta :: printCurrentTypes ();Fruta :: obtenerFruta ( 'Manzana' ); Fruta :: printCurrentTypes ();/* PRODUCCIÓN:Número de instancias creadas: 1 AppleNúmero de instancias creadas: 2 Apple BananaNúmero de instancias creadas: 2 Apple Banana */
Este ejemplo está en Python .
clase Fruta : def __init __ ( self , item : str ) : self.item = item clase FruitCollection : def __init__ ( self ) : self.items = { } def get_fruit ( self , item : str ) - > Fruta : si item no está en self.items : self.items [ item ] = Fruta ( item ) devolver elementos propios [ elemento ]si __name__ == "__main__" : frutas = FruitCollection () imprimir ( frutas . get_fruit ( "Manzana" )) imprimir ( frutas . get_fruit ( "Lima" ))
Este ejemplo se realiza en Ruby y muestra la inicialización diferida de un token de autenticación desde un servicio remoto como Google. La forma en que se almacena en caché @auth_token también es un ejemplo de memorización .
requiere clase 'net/http' Blogger def auth_token @auth_token ||= ( res = Net :: HTTP . post_form ( uri , parámetros )) && obtener_token_de_http_response ( res ) fin # get_token_from_http_response, uri y parámetros se definen más adelante en la clase endb = Blogger . new b . instance_variable_get ( :@auth_token ) # devuelve nil b . auth_token # devuelve token b . instance_variable_get ( :@auth_token ) # devuelve token
Scala tiene soporte integrado para la iniciación diferida de variables. [2]
scala > val x = { println ( "Hola" ); 99 } Hola x : Int = 99 scala > lazy val y = { println ( "¡¡Hola!!" ); 31 } y : Int = < lazy > scala > y Hola !! res2 : Int = 31 scala > y res3 : Int = 31
Este ejemplo está en Smalltalk y es un método de acceso típico para devolver el valor de una variable mediante inicialización perezosa.
altura ^ altura siNil: [ altura := 2.0 ] .
La alternativa "no perezosa" es utilizar un método de inicialización que se ejecuta cuando se crea el objeto y luego utilizar un método de acceso más simple para obtener el valor.
inicializar altura := 2.0 altura ^ altura
Tenga en cuenta que la inicialización perezosa también se puede utilizar en lenguajes no orientados a objetos .
En el campo de la informática teórica , la inicialización diferida [3] (también llamada matriz diferida ) es una técnica para diseñar estructuras de datos que puedan funcionar con memoria que no necesita ser inicializada. Específicamente, supongamos que tenemos acceso a una tabla T de n celdas de memoria no inicializadas (numeradas del 1 al n ), y queremos asignar m celdas de esta matriz, por ejemplo, queremos asignar T [ k i ] := v i para pares ( k 1 , v 1 ), ..., ( k m , v m ) con todos los k i siendo diferentes. La técnica de inicialización diferida nos permite hacer esto en solo O( m ) operaciones, en lugar de gastar O( m + n ) operaciones para inicializar primero todas las celdas de la matriz. La técnica es simplemente asignar una tabla V que almacene los pares ( k i , v i ) en algún orden arbitrario, y escribir para cada i en la celda T [ k i ] la posición en V donde se almacena la clave k i , dejando las otras celdas de T sin inicializar. Esto se puede usar para manejar consultas de la siguiente manera: cuando buscamos en la celda T [ k ] algún k , podemos verificar si k está en el rango {1, ..., m }: si no lo está, entonces T [ k ] no está inicializado. De lo contrario, verificamos V [ T [ k ]], y verificamos que el primer componente de este par sea igual a k . Si no lo está, entonces T [ k ] no está inicializado (y simplemente cayó accidentalmente en el rango {1, ..., m }). De lo contrario, sabemos que T [ k ] es de hecho una de las celdas inicializadas, y el valor correspondiente es el segundo componente del par.