Inicialización perezosa

Compartir Imprimir Citar

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 necesita. Es un tipo de evaluación perezosa que se refiere específicamente a la creación de instancias de objetos u otros recursos.

Esto generalmente se logra mediante el aumento de un método de acceso (o captador de propiedad) para verificar si un miembro privado, que actúa como un caché, ya se ha inicializado. Si es así, se devuelve inmediatamente. De lo contrario, se crea una nueva instancia, se coloca en la variable miembro y se devuelve a la persona que llama justo a tiempo para su primer uso.

Si los objetos tienen propiedades que rara vez se usan, esto puede mejorar la velocidad de inicio. El rendimiento promedio promedio del programa puede ser ligeramente peor en términos de memoria (para las variables de condición) y ciclos de ejecución (para verificarlos), pero el impacto de la creación de instancias de objetos se distribuye en el tiempo ("amortizado") en lugar de concentrarse en la fase de inicio de un sistema y, por lo tanto, los tiempos de respuesta promedio pueden mejorarse considerablemente.

En el código de subprocesos múltiples, el acceso a los objetos/estados con inicialización diferida debe sincronizarse para protegerse contra las condiciones de carrera.

La "fábrica perezosa"

En una vista de patrón de diseño de software, la inicialización diferida se usa a menudo junto con un patrón de método de fábrica. Esto combina tres ideas:

Ejemplos

Acción Script 3

El siguiente es un ejemplo de una clase con inicialización diferida implementada en ActionScript:

paquete ejemplos.lazyinstantiation{}público clase Frutas {}privado Var _typeName:String;privado estática Var instanciasByTypeName:Diccionario = nuevo Diccionario(); público función Frutas()TipoName:String):vacío{}esto._typeName = TipoName;}público función # TipoName():String {}retorno _typeName;} público estática función getFruitByTypeName()TipoName:String):Frutas{}retorno instanciasByTypeName[TipoName] Silencio nuevo Frutas()TipoName);} público estática función printCurrentTypes():vacío{}para cada uno ()Var fruta:Frutas dentro instanciasByTypeName) {}// itera a través de cada valorrastro()fruta.TipoName);}}}}

Uso básico:

paquete{}importación ejemplos.lazyinstantiation;público clase Main {}público función Main():vacío{}Frutas.getFruitByTypeName()"Banana");Frutas.printCurrentTypes(); Frutas.getFruitByTypeName()"Aplicar");Frutas.printCurrentTypes(); Frutas.getFruitByTypeName()"Banana");Frutas.printCurrentTypes();}}}

C

En C, la evaluación diferida normalmente se implementaría dentro de una sola función, o un solo archivo fuente, usando variables estáticas.

En una función:

#include Identificando.h#include ■stdlib.h#include ■stddef.h#include Identificado.hstruct fruta {} char *Nombre; struct fruta *siguiente; int Número; /* Otros miembros */};struct fruta *get_fruit()char *Nombre) {} estática struct fruta *Fruit_list; estática int seq; struct fruta *f; para ()f = Fruit_list; f; f = f-siguiente) si ()0 == strcmp()Nombre, f-Nombre) retorno f; si ()!()f = malloc()tamaño()struct fruta))) retorno NULL; si ()!()f-Nombre = strdup()Nombre)) {} gratis()f); retorno NULL; } f-Número = ++seq; f-siguiente = Fruit_list; Fruit_list = f; retorno f;}/* Código de ejemplo */int principal()int argc, char *argv[]) {} int i; struct fruta *f; si ()argc . 2) {} fprintf()stderr, "Usage: Fruit-name [...]n"); Salida()1); } para ()i = 1; i . argc; i++) {} si ()f = get_fruit()argv[i]) {} printf()"Fruit %s: number %dn", argv[i] f-Número); } } retorno 0;}

Usar un solo archivo de origen permite que el estado se comparta entre varias funciones, al mismo tiempo que lo oculta de funciones no relacionadas.

fruta.h:

#ifndef _FRUIT_INCLUDED_#define _FRUIT_INCLUDED_struct fruta {} char *Nombre; struct fruta *siguiente; int Número; /* Otros miembros */};struct fruta *get_fruit()char *Nombre);vacío print_fruit_list()QUIERO *archivo);#endif /* _FRUIT_INCLUDED_ */

fruta.c:

#include Identificando.h#include ■stdlib.h#include ■stddef.h#include Identificado.h#include "fruit.h"estática struct fruta *Fruit_list;estática int seq;struct fruta *get_fruit()char *Nombre) {} struct fruta *f; para ()f = Fruit_list; f; f = f-siguiente) si ()0 == strcmp()Nombre, f-Nombre) retorno f; si ()!()f = malloc()tamaño()struct fruta))) retorno NULL; si ()!()f-Nombre = strdup()Nombre)) {} gratis()f); retorno NULL; } f-Número = ++seq; f-siguiente = Fruit_list; Fruit_list = f; retorno f;}vacío print_fruit_list()QUIERO *archivo) {} struct fruta *f; para ()f = Fruit_list; f; f = f-siguiente) fprintf()archivo, "%4d %sn", f-Número, f-Nombre);}

principal.c:

#include ■stdlib.h#include Identificado.h#include "fruit.h"int principal()int argc, char *argv[]) {} int i; struct fruta *f; si ()argc . 2) {} fprintf()stderr, "Usage: Fruit-name [...]n"); Salida()1); } para ()i = 1; i . argc; i++) {} si ()f = get_fruit()argv[i]) {} printf()"Fruit %s: number %dn", argv[i] f-Número); } } printf()"Se han generado los siguientes frutos:n"); print_fruit_list()stdout); retorno 0;}

C#

En.NET Framework 4.0, Microsoft ha incluido una clase Lazy que se puede usar para realizar la carga diferida. A continuación se muestra un código ficticio que realiza la carga diferida de Class Fruit

Var perezoso = nuevo Lazy.Frutas.Frutas fruta = perezoso.Valor;

Este es un ejemplo ficticio en C#.

La clase Fruit en sí misma no hace nada aquí. La variable de clase _typesDictionary es un diccionario/mapa utilizado para almacenar Fruit instancias por typeName.

utilizando Sistema;utilizando Sistema.;utilizando System.Collections.Generic;público clase Frutas{} privado cuerda _typeName; privado estática IDiccionario.cuerda, Frutas _tiposDiccionario = nuevo Diccionario.cuerda, Frutas. privado Frutas()String TipoName) {} esto._typeName = TipoName; } público estática Frutas GetFruitByTypeName()cuerda Tipo) {} Frutas fruta; si (!_tiposDiccionario.TryGetValue()Tipo, Fuera. fruta) {} // Iniciación perezosa fruta = nuevo Frutas()Tipo); _tiposDiccionario.Añadir()Tipo, fruta); } retorno fruta; } público estática vacío ShowAll() {} si ()_tiposDiccionario.Conde  0) {} Consola.WriteLine()"Número de casos hechos = {0}", _tiposDiccionario.Conde);  en adelante ()KeyValuePair.cuerda, Frutas kvp dentro _tiposDiccionario) {} Consola.WriteLine()kvp.Clave); }  Consola.WriteLine(); } }  público Frutas() {} // requerido para que la muestra compila }}clase Programa{} estática vacío Main()cuerda[] args) {} Frutas.GetFruitByTypeName()"Banana"); Frutas.ShowAll(); Frutas.GetFruitByTypeName()"Aplicar"); Frutas.ShowAll(); // devuelve la instancia preexistente de la primera  // tiempo Fruto con "Banana" fue creado Frutas.GetFruitByTypeName()"Banana"); Frutas.ShowAll(); Consola.ReadLine(); }}

Una forma bastante sencilla de 'completar los espacios en blanco' ejemplo de un patrón de diseño de inicialización diferida, excepto que usa una enumeración para el tipo

namespace DesignPatterns.LazyInitialization;público clase LazyFactory Objeto{} //colección interna de artículos //IDictionary se asegura de que son únicos privado IDiccionario.LazyObjectSize, LazyObject _LazyObjectList = nuevo Diccionario.LazyObjectSize, LazyObject. //enum para el nombre de paso del tamaño requerido //evita las cadenas que pasan y es parte de LazyObject delante público enum LazyObjectSize {} Ninguno, Pequeña, Grande, Más grande, Huge } //tipo estándar de objeto que se construirá público struct LazyObject {} público LazyObjectSize Tamaño; público IList.int Resultado; } // toma tamaño y crear la lista 'expensiva' privado IList.int Resultado()LazyObjectSize tamaño) {} IList.int resultado = nulo; interruptor ()tamaño) {} Caso LazyObjectSize.Pequeña: resultado = CreateSomeExpensiveList()1, 100); descanso; Caso LazyObjectSize.Grande: resultado = CreateSomeExpensiveList()1, 1000); descanso; Caso LazyObjectSize.Más grande: resultado = CreateSomeExpensiveList()1, 10000); descanso; Caso LazyObjectSize.Huge: resultado = CreateSomeExpensiveList()1, 100000); descanso; Caso LazyObjectSize.Ninguno: resultado = nulo; descanso; por defecto: resultado = nulo; descanso; } retorno resultado; } //no un artículo caro para crear, pero usted consigue el punto // retarda la creación de algún objeto caro hasta que sea necesario privado IList.int CreateSomeExpensiveList()int Empieza, int final) {} IList.int resultado = nuevo Lista.int. para ()int contra = 0; contra . ()final - Empieza); contra++) {} resultado.Añadir()Empieza + contra); } retorno resultado; } público LazyFactory Objeto() {} //empty constructor } público LazyObject GetLazyFactory Objeto()LazyObjectSize tamaño) {} //sí, sé que es analfabeto e inexacto LazyObject No. Uno; //retrieves LazyObject Tamaño de la lista a través de fuera, de lo contrario crea uno y lo añade a la lista si (!_LazyObjectList.TryGetValue()tamaño, Fuera. No. Uno) {} No. Uno = nuevo LazyObject(); No. Uno.Tamaño = tamaño; No. Uno.Resultado = esto.Resultado()tamaño); _LazyObjectList.Añadir()tamaño, No. Uno); } retorno No. Uno; }}

C++

Aquí hay un ejemplo en C++.

#include ■iostream#include Identificado#include Identificadoclase Frutas {} público: estática Frutas* GetFruit()const std::cuerda" Tipo); estática vacío PrintCurrentTypes(); privado: // Nota: constructor privado forcing one to use static tenciónGetFruit imper. Frutas()const std::cuerda" Tipo) : Tipo_()Tipo) {} estática std::mapa.std::cuerda, Frutas* tipos; std::cuerda Tipo_;};// estáticastd::mapa.std::cuerda, Frutas* Frutas::tipos;// Método Lazy Factory, consigue la instancia ←Fruit// Silencioso. Crea nuevos necesarios.Frutas* Fruit:GetFruit()const std::cuerda" Tipo) {} auto [es, insertado] = tipos.emplace()Tipo, nullptr); si ()insertado) {} es-segundo = nuevo Frutas()Tipo); } retorno es-segundo;}// For example purposes to see pattern in action.vacío Fruit::PrintCurrentTypes() {} std::Cout .. "Número de casos hechos = " .. tipos.tamaño() .. std::endl; para ()const auto" [Tipo, fruta] : tipos) {} std::Cout .. Tipo .. std::endl; } std::Cout .. std::endl;}int principal() {} Frutas::GetFruit()"Banana"); Frutas::PrintCurrentTypes(); Frutas::GetFruit()"Aplicar"); Frutas::PrintCurrentTypes(); // Retorno instancia preexistente desde la primera vez TENFruit sufrimiento con "Banana" fue // creado. Frutas::GetFruit()"Banana"); Frutas::PrintCurrentTypes();}// OutPUT://// Número de casos realizados = 1// Banana//// Número de casos realizados = 2// Apple// Banana//// Número de casos realizados = 2// Apple// Banana//

Cristal

clase Frutas privado Getter Tipo : String @@types = {} de String = Frutas def inicialización()@type) final def auto.get_fruit_by_type()Tipo : String) @@types[Tipo] Silencio Frutas.nuevo()Tipo) final def auto.show_all puts "Número de instancias realizadas: #{@@types.tamaño}" @@types.cada uno do SilencioTipo, frutaSilencio puts "#{Tipo}" final puts final def auto.tamaño @@types.tamaño finalfinalFrutas.get_fruit_by_type()"Banana")Frutas.show_allFrutas.get_fruit_by_type()"Aplicar")Frutas.show_allFrutas.get_fruit_by_type()"Banana")Frutas.show_all

Salida:

Número de casos: 1
Banana

Número de casos: 2
Banana
Apple

Número de casos: 2
Banana
Apple

Haxe

Aquí hay un ejemplo en Haxe

clase Frutas {} privado estática Var _instances = nuevo Mapa.String, Frutas. público Var Nombre()por defecto, nulo):String; público función nuevo()Nombre:String) {} esto.Nombre = Nombre; } público estática función getFruitByName()Nombre:String):Frutas {} si ()!_instances.existe()Nombre) {} _instances.set()Nombre, nuevo Frutas()Nombre)); } retorno _instances.#()Nombre); } público estática función impresión Todos Tipos() {} rastro[para()clave dentro _instances.llaves()) clave]); }}

Uso

clase Prueba {} público estática función principal () {} Var banana = Frutas.getFruitByName()"Banana"); Var manzana = Frutas.getFruitByName()"Aplicar"); Var banana2 = Frutas.getFruitByName()"Banana");  rastro()banana == banana2); - Cierto. misma banana  Frutas.impresión Todos Tipos(); // ["Banana", "Apple"] }}

Java

Aquí hay un ejemplo en Java.

importación java.util.HashMap;importación Java.util. Mapa;importación Java.util. Mapa. Entrada;público clase Programa {} * * @param args */ público estática vacío principal()String[] args) {} Frutas.getFruitByTypeName()FruitType.banana); Frutas.showAll(); Frutas.getFruitByTypeName()FruitType.manzana); Frutas.showAll(); Frutas.getFruitByTypeName()FruitType.banana); Frutas.showAll(); }}enum FruitType {} ninguno, manzana, banana,}clase Frutas {} privado estática Mapa.FruitType, Frutas tipos = nuevo HashMap(); * * Usar un constructor privado para forzar el uso del método de fábrica. * Tipo @param */ privado Frutas()FruitType Tipo) {} } * * Método de fábrica perezosa, consigue la instancia de fruta asociada con un determinado * tipo. Instantiates nuevos según sea necesario. * Tipo @param Cualquier tipo de fruta permitido, por ejemplo. *Retorno La instancia Fruta asociada a ese tipo. */ público estática Frutas getFruitByTypeName()FruitType Tipo) {} Frutas fruta; // Esto tiene problemas de coincidencia. Aquí la lectura a los tipos no se sincroniza,  // así tipos.put y tipos. contiene La llave podría llamarse al mismo tiempo. // No se sorprenda si los datos están dañados. si ()!tipos.contiene Clave()Tipo) {} // Iniciación perezosa fruta = nuevo Frutas()Tipo); tipos.#()Tipo, fruta); } más {} // OK, está disponible actualmente fruta = tipos.#()Tipo); } retorno fruta; } * * Método de fábrica perezosa, consigue la instancia de fruta asociada con un determinado * tipo. Instantiates nuevos según sea necesario. Usos de bloqueo de doble control  * patrón para usar en entornos altamente concurrentes. * Tipo @param Cualquier tipo de fruta permitido, por ejemplo. *Retorno La instancia Fruta asociada a ese tipo. */ público estática Frutas getFruitByTypeNameHighConcurrentVersion()FruitType Tipo) {} si ()!tipos.contiene Clave()Tipo) {} sincronizado ()tipos) {} // Verificar de nuevo, después de haber adquirido la cerradura para asegurarse // la instancia no fue creada mientras tanto por otro hilo si ()!tipos.contiene Clave()Tipo) {} // Iniciación perezosa tipos.#()Tipo, nuevo Frutas()Tipo)); } } } retorno tipos.#()Tipo); } * * Muestra todos los frutos introducidos. */ público estática vacío showAll() {} si ()tipos.tamaño()  0) {} Sistema.Fuera..println()"Número de casos hechos = " + tipos.tamaño()); para ()Entrada.FruitType, Frutas entrada : tipos.entradaSet()) {} String fruta = entrada.# Clave().toString(); fruta = Cara.toUpperCase()fruta.charAt()0) + fruta.subestring()1); Sistema.Fuera..println()fruta); } Sistema.Fuera..println(); } }}

Salida

Número de casos realizados = 1
Banana

Número de casos hechos = 2
Banana
Apple

Número de casos hechos = 2
Banana
Apple

Javascript

Aquí hay un ejemplo en JavaScript.

Var Frutas = ()función() {} Var tipos = {} función Frutas() {} // Contar propiedades propias en objeto función Cuenta()obj) {} retorno Objeto.llaves()obj).longitud; } Var Estática = {} getFruit: función()Tipo) {} si ()tipo de tipos[Tipo] == 'undefinido ') {} tipos[Tipo] = nuevo Frutas; } retorno tipos[Tipo]; } printCurrentTypes: función () {} consola.log()'Número de instancias realizadas: ' + Cuenta()tipos)); para ()Var Tipo dentro tipos) {} consola.log()Tipo); } } }; retorno Estática;})();Frutas.getFruit()'Apple ');Frutas.printCurrentTypes();Frutas.getFruit()'Banana' ');Frutas.printCurrentTypes();Frutas.getFruit()'Apple ');Frutas.printCurrentTypes();

Salida

Número de casos: 1
Apple

Número de casos: 2
Apple
Banana

Número de casos: 2
Apple
Banana

PHP

Este es un ejemplo de inicialización diferida en PHP 7.4:

Identificado?phpheader()'Content-Type: text/plain; charset=utf-8 ');clase Frutas{} privado cuerda Tipo de $; privado estática array Tipos de dólares = array(); privado función __construir()cuerda Tipo de $) {} Esto-Tipo = Tipo de $; } público estática función getFruit()cuerda Tipo de $) {} // La inicialización perezosa tiene lugar aquí si ()!isset()auto::tipos[Tipo de $]) {} auto::tipos[Tipo de $] = nuevo Frutas()Tipo de $); } retorno auto::tipos[Tipo de $]; } público estática función printCurrentTypes(): vacío {} eco 'Número de instancias realizadas: ' . Cuenta()auto::tipos) . "n"; en adelante ()array_keys()auto::tipos) como $key) {} eco "#"; } eco "n"; }}Frutas::getFruit()'Apple ');Frutas::printCurrentTypes();Frutas::getFruit()'Banana' ');Frutas::printCurrentTypes();Frutas::getFruit()'Apple ');Frutas::printCurrentTypes();/*Fuera.Número de casos: 1AppleNúmero de casos: 2AppleBananaNúmero de casos: 2AppleBanana*/

Pitón

Aquí hay un ejemplo en Python.

clase Frutas: def __init_()auto, Tema: str) - Ninguno: auto.Tema = Temaclase Frutas: def __init_()auto) - Ninguno: auto.Temas = {} def get_fruit()auto, Tema: str) - Frutas: si Tema no dentro auto.Temas: auto.Temas[Tema] = Frutas()Tema) retorno auto.Temas[Tema]si __name_ == "__main__": frutas = Frutas() impresión()frutas.get_fruit()"Aplicar") impresión()frutas.get_fruit()"Lime")

Rubí

Este es un ejemplo en Ruby, de la inicialización perezosa de un token de autenticación desde un servicio remoto como Google. La forma en que @auth_token se almacena en caché también es un ejemplo de memorización.

necesidad 'net/http 'clase Blogger def auth_token @auth_token Silencio ()res = Cifras netas::HTTP.post_form()uri, params) " get_token_from_http_response()res) final # get_token_from_http_response, uri and params are defined later in the classfinalb = Blogger.nuevob.instance_variable_get():@auth_token) # Returns nilb.auth_token # Returns tokenb.instance_variable_get():@auth_token) # Returns token

Escala

Scala tiene soporte incorporado para la iniciación de variables diferidas.

 scala val x = {} println()"Hola"); 99 } Hola. x: Int = 99 scala perezoso val Sí. = {} println()"¡Hola!"); 31 } Sí.: Int = .perezoso scala Sí. Hola.! res2: Int = 31 scala Sí. res3: Int = 31

Pequeñas conversaciones

Aquí hay un ejemplo en Smalltalk, de un método de acceso típico para devolver el valor de una variable usando inicialización diferida.

 altura ^altura SiNil: [altura := 2.0].

El 'no perezoso' La alternativa es usar un método de inicialización que se ejecuta cuando se crea el objeto y luego usar un método de acceso más simple para obtener el valor.

 inicialización altura := 2.0 altura ^altura

Tenga en cuenta que la inicialización diferida también se puede utilizar en lenguajes no orientados a objetos.

Informática teórica

En el campo de la informática teórica, la inicialización diferida (también denominada matriz perezosa) es una técnica para diseñar estructuras de datos que pueden funcionar con memoria que no necesita para ser inicializado. Específicamente, supongamos que tenemos acceso a una tabla T de n celdas de memoria no inicializadas (numeradas de 1 a n), y queremos asignar m celdas de esta matriz, por ejemplo, queremos asignar T[ki ]:= vi para pares (k1, v 1),..., (km, vm) con todos los ki siendo diferente. La técnica de inicialización diferida nos permite hacer esto en solo operaciones O(m), en lugar de gastar operaciones O(m+n) para primero inicializar todas las celdas de la matriz. La técnica consiste simplemente en asignar una tabla V que almacena los pares (ki, vi) en algún orden arbitrario, y escribir para cada i en la celda T[ki ] la posición en V donde se almacena la clave ki, 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 es igual a k. Si no es así, entonces T[k] no está inicializado (y por accidente cayó 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.