Articulo de referencia

Inicialización perezosa

En programación informática , la inicialización perezosa es la táctica de retrasar la creación de un objeto , el cálculo de un valor u otro proceso costoso hasta que sea necesar...

En programación informática , la inicialización perezosa es la táctica de retrasar la creación de un objeto , el cálculo de un valor u otro proceso costoso hasta que sea necesario por primera vez. Se trata de una forma de evaluación perezosa que se refiere específicamente a la instanciación de objetos u otros recursos.

Esto se suele lograr modificando un método de acceso (o un método getter de propiedad) para comprobar si un miembro privado, que actúa como caché, ya se ha inicializado. Si es así, se devuelve inmediatamente. Si no, se crea una nueva instancia, se coloca en la variable miembro y se devuelve a quien la llama justo a tiempo para su primer uso.

Si los objetos tienen propiedades que se usan con poca frecuencia, 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 comprobarlas), pero el impacto de la instanciación de objetos se distribuye en el tiempo ("amortiza") en lugar de concentrarse en la fase de inicio del sistema, por lo que los tiempos de respuesta promedio pueden mejorarse considerablemente.

En el código multihilo , el acceso a objetos/estados inicializados de forma diferida debe estar sincronizado para evitar condiciones de carrera .

La "fábrica perezosa"

Desde la perspectiva de los patrones 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:

  • Utilizar un método de fábrica para crear instancias de una clase ( patrón de método de fábrica ).
  • Almacenar las instancias en un mapa y devolver la misma instancia para cada solicitud de una instancia con los mismos parámetros ( patrón multitono ).
  • Utilizar la inicialización diferida para instanciar el objeto la primera vez que se solicita (patrón de inicialización diferida).

Ejemplos

ActionScript 3

A continuación se muestra un ejemplo de una clase con inicialización diferida implementada en ActionScript :

package examples . lazyinstantiation { public class Fruit { private var _typeName : String ; private static var instancesByTypeName : Dictionary = new Dictionary (); public function Fruit ( typeName : String ) : void { this . _typeName = typeName ; } public function get typeName () : String { return _typeName ; } public static function getFruitByTypeName ( typeName : String ) : Fruit { return instancesByTypeName [ typeName ] ||= new Fruit ( typeName ); } public static function printCurrentTypes () : void { for each ( var fruit : Fruit in instancesByTypeName ) { // itera a través de cada valor trace ( fruit . typeName ); } } } }

Uso básico:

package { import examples . lazyinstantiation ; public class Main { public function Main () : void { Fruit . getFruitByTypeName ( "Banana" ); Fruit . printCurrentTypes (); Fruit . getFruitByTypeName ( "Apple" ); Fruit . printCurrentTypes (); Fruit . getFruitByTypeName ( "Banana" ); Fruit . printCurrentTypes (); } } }

do

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 <stddef.h> #include <stdlib.h> #include <stdio.h> #include <string.h>typedef struct Fruit { char * name ; struct Fruit * next ; int number ; // Otros miembros } Fruit ;Fruta * obtenerFruta ( char * nombre ) { static Fruta * listaFrutas ; static int seq ; Fruta * f = listaFrutas ;while ( f ) { if ( ! strcmp ( name , f -> name )) { return f ; } f = f -> next ; }if ( ! ( f = malloc ( sizeof ( Fruit )))) { return NULL ; }if ( ! ( f -> nombre = strdup ( nombre ))) { free ( f ); return NULL ; }f -> número = ++ seq ; f -> siguiente = listaFrutas ; listaFrutas = f ; return f ; }// Código de ejemploint main ( int argc , char * argv []) { Fruit * f ;if ( argc < 2 ) { fprintf ( stderr , "Uso: fruits fruit-name [...] \n " ); return 1 ; }for ( int i = 1 ; i < argc ; i ++ ) { if ( f = getFruit ( argv [ i ])) { printf ( "Fruta %s: número %d \n " , argv [ i ], f -> number ); } }devolver 0 ; }

En cambio, al usar un único archivo fuente, el estado se puede compartir entre varias funciones, al tiempo que se oculta a las funciones no relacionadas.

Fruta.h :

#pragma una veztypedef struct Fruit { char * name ; struct Fruit * next ; int number ; // Otros miembros } Fruit ;Fruta * obtenerFruta ( char * nombre ); void imprimirListaFruta ( FILE * archivo );

Fruta.c :

#include <stddef.h> #include <stdlib.h> #include <stdio.h> #include <string.h>#incluir "Fruit.h"static Fruit * fruitList ; static int seq ;struct Fruit * getFruit ( char * name ) { Fruit * f = fruitList ;while ( f ) { if ( ! strcmp ( name , f -> name )) { return f ; } f = f -> next ; }if ( ! ( f = malloc ( sizeof ( Fruit )))) { return NULL ; }if ( ! ( f -> nombre = strdup ( nombre ))) { free ( f ); return NULL ; }f -> número = ++ seq ; f -> siguiente = listaFrutas ; listaFrutas = f ; return f ; }void printFruitList ( FILE * file ) { Fruit * f = fruitList ; while ( f ) { fprintf ( file , "%4d %s \n " , f -> number , f -> name ); f = f -> next ; } }

Main.c :

#include <stdlib.h> #include <stdio.h>#incluir "Fruit.h"int main ( int argc , char * argv []) { Fruit * f ;if ( argc < 2 ) { fprintf ( stderr , "Uso: fruits fruit-name [...] \n " ); return 1 ; }for ( int i = 1 ; i < argc ; i ++ ) { if ( f = getFruit ( argv [ i ])) { printf ( "Fruta %s: número %d \n " , argv [ i ], f -> number ); } }printf ( "Se han generado las siguientes frutas: \n " ); printFruitList ( stdout ); return 0 ; }

DO#

En .NET Framework 4.0, Microsoft ha incluido una System.Lazyclase que se puede utilizar para realizar la carga diferida. A continuación se muestra un código de ejemplo que realiza la carga diferida de la clase.Fruit

usando el sistema ;Perezoso < Fruta > frutaperezosa = nuevo (); Fruta fruta = frutaperezosa . Valor ;

Aquí hay un ejemplo ficticio en C# .

La Fruitclase en sí no hace nada aquí, la variable de clase_typesDictionary es un diccionario/mapa utilizado para almacenar Fruitinstancias por typeName.

espacio de nombres Wikipedia.Ejemplos ;using System ; using System.Collections ; using System.Collections.Generic ;clase Fruta { cadena privada _typeName ; diccionario estático privado < cadena , Fruta > _typesDictionary = nuevo Diccionario < cadena , Fruta > ();private Fruit ( string typeName ) { this . _typeName = typeName ; }public static Fruit GetFruitByTypeName ( string type ) { Fruit fruit ;if ( ! _typesDictionary . TryGetValue ( type , out fruit )) { // Inicialización perezosa fruit = new Fruit ( type );_typesDictionary.Add ( type , fruit ) ; }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 el ejemplo compile } }public class Program { static void Main ( string [] args ) { Fruit . GetFruitByTypeName ( "Banana" ); Fruit . ShowAll ();Fruta.GetFruitByTypeName ( "Manzana " ) ; Fruta.ShowAll ( ) ;// devuelve una instancia preexistente de la primera vez // que se creó Fruit con "Banana" Fruit . GetFruitByTypeName ( "Banana" ); Fruit . ShowAll ();Consola.LeerLínea ( ) ; } }

Un ejemplo bastante sencillo de "completar los espacios en blanco" de un patrón de diseño de inicialización perezosa, excepto que este utiliza una enumeración para el tipo.

espacio de nombres Wikipedia.Ejemplos ;using System ; using System.Collections.Generic ;public class 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 por delante 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 'costosa' private IList < int > Result ( LazyObjectSize size ) { IList < int > result = null ;switch ( size ) { case LazyObjectSize . Small : result = CreateSomeExpensiveList ( 1 , 100 ); break ; case LazyObjectSize . Big : result = CreateSomeExpensiveList ( 1 , 1000 ); break ; case LazyObjectSize . Bigger : result = CreateSomeExpensiveList ( 1 , 10000 ); break ; case LazyObjectSize . Huge : result = CreateSomeExpensiveList ( 1 , 100000 ); break ; case LazyObjectSize . None : result = null ; break ; default : result = null ; break ; }devolver resultado ; }// no es un elemento costoso de crear, pero entiendes la idea // 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 > ();for ( int counter = 0 ; counter < ( end - start ); counter ++ ) { result . Add ( start + counter ); }devolver resultado ; }public LazyFactoryObject () { // constructor vacío }public LazyObject GetLazyFactoryObject ( LazyObjectSize size ) { // sí, sé que es ilegible 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 ( size , noGoodSomeOne ) ; }devolver noGoodSomeOne ; } }

C++

Este ejemplo está en C++ .

importar std ;plantilla < typename K , typename V > usando HashMap = std :: unordered_map < K , V > ; plantilla < typename T > usando SharedPtr = std :: shared_ptr < T > ; usando String = std :: string ;clase Fruta { privado : static HashMap < String , SharedPtr <Fruit> > tipos = {} ; String tipo ;// Nota: constructor privado que obliga a usar static getFruit. explicit Fruit ( const String & type ) : type { type } {} public : // Método de fábrica perezoso, obtiene la instancia de Fruit asociada con un tipo determinado. // Crea nuevas instancias según sea necesario. static SharedPtr < Fruit > getFruit ( const String & type ) { auto [ it , inserted ] = types . emplace ( type , nullptr ); if ( inserted ) { it -> second = std :: make_shared < Fruit > ( type ); } return it -> second ; }// A modo de ejemplo para ver el patrón en acción. static void printCurrentTypes () { std :: println ( "Número de instancias creadas = {}" , types . size ()); for ( const auto & [ type , fruit ] : types ) { std :: println ({}, type ); } std :: println (); } };int main ( int argc , char * argv []) { Fruit :: getFruit ( "Banana" ); Fruit :: printCurrentTypes ();Fruta :: obtenerFruta ( "Manzana" ); Fruta :: printCurrentTypes ();// Devuelve una instancia preexistente de la primera vez que se creó Fruit con "Banana". Fruit :: getFruit ( "Banana" ); Fruit :: printCurrentTypes (); }// SALIDA: // // Número de instancias creadas = 1 // Plátano // // Número de instancias creadas = 2 // Manzana // Plátano // // Número de instancias creadas = 2 // Manzana // Plátano //

Cristal

clase Fruit tipo getter privado : String @@types = {} de String => Fruitdef inicializar ( @type ) findef self.get_fruit_by_type ( type : String ) @@ types [ type ] || = Fruit.new ( type ) enddef self.show_all puts "Número de instancias creadas : # { @@ types.size } " @@ types.each do | type , fruit | puts " # { type } " end puts enddef self.size @@ types.size end endFruta.get_fruit_by_type ( " Banana " ) Fruta.show_allFruta.get_fruit_by_type ( " Manzana " ) Fruta.show_allFruta.get_fruit_by_type ( " Banana " ) Fruta.show_all

Producción:

Número de instancias realizadas: 1 Banana Número de instancias realizadas: 2 Banana Manzana Número de instancias realizadas: 2 Banana Manzana 

Haxe

Este ejemplo está en Haxe . [ 1 ]

clase Fruta { private static var _instancias = new Map < String , Fruta >();public var nombre ( predeterminado , null ): String ;función pública nuevo ( nombre : String ) { this . nombre = nombre ; }public static function getFruitByName ( name : String ): Fruit { if ( ! _instances . exists ( name )) { _instances . set ( name , new Fruit ( name )); } return _instances . get ( name ); }public static function printAllTypes () { trace ([ for ( key in _instances . keys ()) key ]); } }

Uso

clase Test { public static function main () { var banana = Fruit . getFruitByName ( "Banana" ); var apple = Fruit . getFruitByName ( "Apple" ); var banana2 = Fruit . getFruitByName ( "Banana" ); trace ( banana == banana2 ); // true. misma banana Fruit . printAllTypes (); // ["Banana","Apple"] } }

Java

Este ejemplo está en Java .

paquete org.wikipedia.examples ;import java.util.HashMap ; import java.util.Map ;enum FruitType { NINGUNO , MANZANA , PLÁTANO , }clase Fruit { 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  * tipo determinado. Instancia nuevas instancias según sea necesario.  * @param type Cualquier tipo de fruta permitido, por ejemplo, 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 a types 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 corruptos. 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 de fábrica perezoso, obtiene la instancia de Fruit asociada con un * tipo determinado  . Instancia nuevas instancias según sea necesario. Utiliza un patrón de bloqueo de doble verificación  * para su uso en entornos altamente concurrentes.  * @param type Cualquier tipo de fruta permitido, por ejemplo, MANZANA  * @return La instancia de Fruit asociada con ese tipo.  */ public static Fruit getFruitByTypeNameHighConcurrentVersion ( FruitType type ) { if ( ! types . containsKey ( type )) { synchronized ( types ) { // Comprobar de nuevo, después de haber adquirido el bloqueo para asegurarse de que // 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( tipo ); } /**  * Muestra todas las frutas ingresadas.  */ public static void showAll () { if ( types . size () > 0 ) { System . out . printf ( "Número de instancias creadas = %d%n" , types . size ()); for ( Map . 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 (); } } }clase pública Programa {/**  * @param args  */ public static void main ( String [] args ) { Fruta . getFruitByTypeName ( FruitType . BANANA ); Fruta . mostrar todo (); Fruta . getFruitByTypeName ( FruitType . MANZANA ); Fruta . mostrar todo (); Fruta . getFruitByTypeName ( FruitType . BANANA ); Fruta . mostrar todo (); } }

Producción

Número de instancias creadas = 1 Banana Número de instancias creadas = 2 Banana Manzana Número de instancias creadas = 2 Banana Manzana 

JavaScript

Este ejemplo está en JavaScript .

var Fruta = ( función () { var tipos = {}; función Fruta () {};// cuenta las propiedades propias en el objeto function count ( obj ) { return Object . keys ( obj ). length ; }var _static = { getFruit : function ( type ) { if ( typeof types [ type ] == 'undefined' ) { types [ type ] = new Fruit ; } return types [ type ]; }, printCurrentTypes : function () { console . log ( 'Número de instancias creadas: ' + count ( types )); for ( var type in types ) { console . log ( type ); } } };devolver _estático ;})();Fruta . obtenerFruta ( 'Manzana' ); Fruta . imprimirTiposActuales (); Fruta . obtenerFruta ( 'Plátano' ); Fruta . imprimirTiposActuales (); Fruta . obtenerFruta ( 'Manzana' ); Fruta . imprimirTiposActuales ();

Producción

Número de instancias realizadas: 1 Manzana Número de instancias realizadas: 2 Manzana Banana Número de instancias realizadas: 2 Manzana Banana 

PHP

Aquí tenéis un ejemplo de inicialización diferida en PHP 7.4:

<?php header ( 'Content-Type: text/plain; charset=utf-8' );clase Fruta { cadena privada $type ; matriz estática privada $types = matriz ();función privada __construct ( cadena $type ) { $this -> type = $type ; }public static function getFruit ( string $type ) : Fruit { // La inicialización diferida tiene lugar aquí if ( ! isset ( self :: $types [ $type ])) { self :: $types [ $type ] = new Fruit ( $type ); }return self :: $types [ $type ]; }public static function printCurrentTypes () : void { echo 'Número de instancias creadas: ' . count ( self :: $types ) . " \n " ; foreach ( array_keys ( self :: $types ) as $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 realizadas: 2 Manzana PlátanoNúmero de instancias realizadas: 2 Manzana Plátano * /

Pitón

Este ejemplo está en Python .

clase Fruta : def __init __ ( self , item : str ) - > None : self.item = itemclase FruitCollection : def __init__ ( self ) - > None : self.items : dict [ str , Fruit ] = { }def obtener_fruta ( self , item : str ) -> Fruta : if item not in self . items : self . items [ item ] = Fruta ( item )devolver self.items [ item ]if __name__ == "__main__" : fruits : FruitCollection = FruitCollection () print ( fruits . get_fruit ( "Apple" )) print ( fruits . get_fruit ( "Lime" ))

Rubí

Este ejemplo, escrito en Ruby , muestra cómo inicializar de forma diferida 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 .

require 'net/http' class Blogger def auth_token @auth_token ||= ( res = Net :: HTTP . post_form ( uri , params )) && get_token_from_http_response ( res ) end# get_token_from_http_response, uri y params se definen más adelante en el final de la clase.b = Blogger.new b.instance_variable_get ( : @auth_token ) # devuelve nil b.auth_token # devuelve token b.instance_variable_get ( : @ auth_token ) # devuelve token

Óxido

El óxido tiene std::cell::LazyCell. [ 2 ]

use std :: cell :: LazyCell ;let lazy : LazyCell = LazyCell :: new ( || 42 );

Scala

Scala tiene soporte incorporado para la inicialización diferida de variables. [ 3 ]

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

Charla informal

Este ejemplo, escrito en Smalltalk , muestra un método de acceso típico para devolver el valor de una variable mediante inicialización diferida.

altura ^ altura si es nulo: [ altura := 2.0 ] .

La alternativa "no perezosa" consiste en utilizar un método de inicialización que se ejecute cuando se crea el objeto y, a continuación, utilizar un método de acceso más sencillo para obtener el valor.

inicializar altura := 2.0altura ^ 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 perezosa [ 4 ] (también llamada arreglo perezoso ) es una técnica para diseñar estructuras de datos que pueden trabajar 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 este arreglo, por ejemplo, queremos asignar T [ k i ] := v i para pares ( k 1 , v 1 ), ..., ( k m , v m ) donde todos los k i son diferentes. La técnica de inicialización perezosa nos permite hacer esto en solo O( m ) operaciones, en lugar de gastar O( m + n ) operaciones para inicializar primero todas las celdas del arreglo. La técnica consiste simplemente en 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 demás celdas de T sin inicializar. Esto se puede usar para manejar consultas de la siguiente manera: cuando buscamos la celda T [ k ] para algún k , podemos verificar si T [ k ] está en el rango {1, ..., m }: si no lo está, entonces T [ k ] no está inicializada. De lo contrario, verificamos V [ T [ k ]] y comprobamos que el primer componente de este par sea igual a k . Si no lo es, entonces T [ k ] no está inicializada (y simplemente cayó por casualidad en el rango {1, ..., m }). De lo contrario, sabemos que T [ k ] es efectivamente una de las celdas inicializadas, y el valor correspondiente es el segundo componente del par.

Véase también

Referencias

  1. ^ "Inicialización perezosa - Patrones de diseño - Libro de recetas del lenguaje de programación Haxe" . 11 de enero de 2018. Consultado el 9 de noviembre de 2018 .
  2. ^ "LazyCell en std::cell - Rust" . doc.rust-lang.org . Consultado el 18 de enero de 2025 .
  3. ^ Pollak, David (25 de mayo de 2009). Introducción a Scala . Apress. ISBN 9781430219897.
  4. ^ Moret, BME; Shapiro, HD (1991). Algoritmos de P a NP, Volumen 1: Diseño y Eficiencia . Benjamin/Cummings Publishing Company. págs.  191–192 . ISBN 0-8053-8008-6.
  • Artículo " Java Tip 67: Instanciación perezosa : equilibrio entre rendimiento y uso de recursos" de Philip Bishop y Nigel Warren
  • Ejemplos de código Java
  • Utilice la inicialización diferida para ahorrar recursos.
  • Descripción del repositorio de patrones de Portland
  • Inicialización diferida de los servicios del servidor de aplicaciones
  • Herencia perezosa en JavaScript
  • Herencia perezosa en C#
Obtenido de " https://en.wikipedia.org/w/index.php?title=Lazy_initialization&oldid=1320827387 "