В компьютерном программировании , ленивая инициализация является тактикой затягивания создания объекта , то вычисления значения , или каким - либо другой дорогостоящего процесса , пока впервые не нужно. Это своего рода ленивая оценка, которая относится конкретно к созданию экземпляров объектов или других ресурсов.
Обычно это достигается путем расширения метода доступа (или средства получения свойства), чтобы проверить, был ли уже инициализирован частный член, действующий как кеш. Если есть, то сразу же возвращают. В противном случае создается новый экземпляр, помещается в переменную-член и возвращается вызывающей стороне точно в срок для его первого использования.
Если объекты имеют редко используемые свойства, это может повысить скорость запуска. Средняя средняя производительность программы может быть немного хуже с точки зрения памяти (для переменных состояния) и циклов выполнения (для их проверки), но влияние создания экземпляра объекта распределяется во времени («амортизируется»), а не концентрируется на этапе запуска система, и, таким образом, среднее время отклика может быть значительно улучшено.
В многопоточном коде доступ к лениво инициализированным объектам / состоянию должен быть синхронизирован для защиты от условий гонки .
«Ленивая фабрика» [ править ]
В представлении шаблона проектирования программного обеспечения отложенная инициализация часто используется вместе с шаблоном фабричного метода . Это объединяет три идеи:
- Использование фабричного метода для создания экземпляров класса ( фабричный шаблон метода )
- Сохранение экземпляров на карте и возврат одного и того же экземпляра в каждый запрос для экземпляра с одинаковыми параметрами ( многотонный шаблон )
- Использование ленивой инициализации для создания экземпляра объекта при первом запросе (шаблон ленивой инициализации)
Примеры [ править ]
ActionScript 3 [ править ]
Ниже приведен пример класса с отложенной инициализацией, реализованной в ActionScript :
примеры пакетов . ленивое создание { открытый класс Fruit { частный var _typeName : String ; частный статический var instanceByTypeName : Dictionary = new Dictionary (); публичная функция Fruit ( typeName : String ) : void { this . _typeName = typeName ; }публичная функция получить typeName () : String { return _typeName ; } общедоступная статическая функция getFruitByTypeName ( typeName : String ) : Fruit { return instanceByTypeName [ typeName ] || = new Fruit ( typeName ); } общедоступная статическая функция printCurrentTypes () : void { для каждого ( var fruit : Fruit in instanceByTypeName ) { // итерация по трассировке каждого значения ( fruit . typeName ); } } } }
Основное использование:
package { импортные примеры . ленивое создание ;публичный класс Main { публичная функция Main () : void { Fruit . getFruitByTypeName ( "Банан" ); Фрукты . printCurrentTypes (); Фрукты . getFruitByTypeName ( "Яблоко" ); Фрукты . printCurrentTypes (); Фрукты . getFruitByTypeName ( "Банан" ); Фрукты . printCurrentTypes (); } } }
C [ править ]
В C ленивое вычисление обычно реализуется внутри одной функции или одного исходного файла с использованием статических переменных .
В функции:
#include <string.h>#include <stdlib.h>#include <stddef.h>#include <stdio.h>struct fruit { char * name ; struct fruit * next ; int число ; / * Другие участники * / };struct fruit * get_fruit ( char * name ) { статическая структура фрукта * fruit_list ; статический int seq ; struct fruit * f ; for ( f = fruit_list ; f ; f = f -> next ) if ( 0 == strcmp ( name , f -> name )) return f ; если ( ! ( f = malloc ( sizeof ( struct fruit )))) return NULL ; если ( ! ( е -> имя = strdup ( имя ))) { бесплатно ( е ); return NULL ; } f -> число = ++ seq ; е -> следующий = список фруктов ; fruit_list = f ; return f ; }/ * Пример кода * /int main ( int argc , char * argv []) { int я ; struct fruit * f ; если ( argc < 2 ) { fprintf ( stderr , "Использование: фрукты имя-фрукта [...] \ n " ); выход ( 1 ); } for ( i = 1 ; i < argc ; i ++ ) { if ((f = get_fruit ( argv [ i ]))) { printf ( "Fruit% s: number% d \ n " , argv [ i ], f -> number ); } } return 0 ; }
Вместо этого использование одного исходного файла позволяет разделить состояние между несколькими функциями, при этом скрывая его от несвязанных функций.
fruit.h:
#ifndef _FRUIT_INCLUDED_ #define _FRUIT_INCLUDED_struct fruit { char * name ; struct fruit * next ; int число ; / * Другие участники * / };struct fruit * get_fruit ( char * имя ); void print_fruit_list ( ФАЙЛ * файл );#endif / * _FRUIT_INCLUDED_ * /
Fruit.c:
#include <string.h>#include <stdlib.h>#include <stddef.h>#include <stdio.h>#include "fruit.h"статическая структура fruit * fruit_list ; статический int seq ;struct fruit * get_fruit ( char * name ) { struct fruit * f ; for ( f = fruit_list ; f ; f = f -> next ) if ( 0 == strcmp ( name , f -> name )) return f ; если ( ! ( f = malloc ( sizeof ( struct фрукты )))) return NULL ; если ( ! ( е -> имя = strdup ( имя ))) { бесплатно ( е ); return NULL ; } f -> число = ++ seq ; е -> следующий = список фруктов ; fruit_list = f ; return f ; }void print_fruit_list ( ФАЙЛ * файл ) { struct fruit * f ; for ( f = fruit_list ; f ; f = f -> next ) fprintf ( file , "% 4d% s \ n " , f -> число , f -> имя ); }
main.c:
#include <stdlib.h>#include <stdio.h>#include "fruit.h"int main ( int argc , char * argv []) { int я ; struct fruit * f ; если ( argc < 2 ) { fprintf ( stderr , "Использование: фрукты имя-фрукта [...] \ n " ); выход ( 1 ); } for ( i = 1 ; i < argc ; i ++ ) { if ((f = get_fruit ( argv [ i ]))) { printf ( "Fruit% s: number% d \ n " , argv [ i ], f -> number ); } } printf ( "Получены следующие плоды: \ n " ); print_fruit_list (стандартный вывод ); возврат 0 ; }
C # [ править ]
В .NET Framework 4.0 Microsoft включила Lazy
класс, который можно использовать для отложенной загрузки. Ниже приведен фиктивный код, который выполняет ленивую загрузку класса.Fruit
var lazyFruit = new Lazy < Fruit > (); Fruit fruit = lazyFruit . Стоимость ;
Вот фиктивный пример на C # .
Сам Fruit
класс здесь ничего не делает. Переменная класса _typesDictionary
- это словарь / карта, используемая для хранения Fruit
экземпляров typeName
.
используя Систему ; using System.Collections ; using System.Collections.Generic ;открытый класс Fruit { частная строка _typeName ; частный статический IDictionary < string , Fruit > _typesDictionary = new Dictionary < string , Fruit > (); частный фрукт ( String typeName ) { this . _typeName = typeName ; } общедоступный статический Fruit GetFruitByTypeName ( строковый тип ) { Fruit fruit ; if (! _typesDictionary . TryGetValue ( type , out fruit )) { // Ленивая инициализация fruit = new Fruit ( type ); _typesDictionary . Добавить ( вид , фрукт ); } вернуть фрукты ; } public static void ShowAll () { if ( _typesDictionary . Count > 0 ) { Console . WriteLine ( "Количество созданных экземпляров = {0}" , _typesDictionary . Count ); foreach ( KeyValuePair < string , Fruit > kvp в _typesDictionary ) { Console . WriteLine ( квп . Ключ ); } Консоль . WriteLine (); } } public Fruit () { // требуется для компиляции образца } }class Program { static void Main ( string [] args ) { Fruit . GetFruitByTypeName ( «Банан» ); Фрукты . ShowAll (); Фрукты . GetFruitByTypeName ( «Яблоко» ); Фрукты . ShowAll (); // возвращает уже существующий экземпляр // при первом создании Fruit с "Banana" Fruit . GetFruitByTypeName ( «Банан» ); Фрукты . ShowAll (); Консоль . ReadLine (); } }
Довольно простой пример шаблона проектирования отложенной инициализации с заполнением пробелов, за исключением того, что в нем используется перечисление для типа
namespace DesignPatterns.LazyInitialization { public class LazyFactoryObject { // внутренняя коллекция элементов // IDictionaery гарантирует, что они являются уникальными частными IDictionary < LazyObjectSize , LazyObject > _LazyObjectList = new Dictionary < LazyObjectSize , LazyObject > (); // перечисление для передачи имени требуемого размера // избегает передачи строк и является частью LazyObject впереди публичного перечисления LazyObjectSize { None , Small , Big , Bigger , Huge } // стандартный тип объекта, который будет создан public struct LazyObject { public LazyObjectSize Size ; public IList < int > Результат ; } // принимает размер и создает «дорогой» список private IList < int > Result ( LazyObjectSize size ) { IList < int > result = null ; переключатель ( размер ) { case LazyObjectSize . Маленький : результат = CreateSomeExpectList ( 1 , 100 ); перерыв ; case LazyObjectSize . Большой : результат = CreateSomeExrivateList ( 1 , 1000 ); перерыв ; case LazyObjectSize . Больше : результат = CreateSomeExuritiesList ( 1 , 10000 ); перерыв; case LazyObjectSize . Огромный : результат = CreateSomeExuritiesList ( 1 , 100000 ); перерыв ; case LazyObjectSize . Нет : результат = ноль ; перерыв ; по умолчанию : результат = ноль ; перерыв ; } вернуть результат ; } // создание элемента не является дорогостоящим, но вы понимаете, // что создание дорогостоящего объекта откладывается до тех пор, пока не понадобится частный IList < int > CreateSomeExuritiesList ( int start , int end ) { IList < int > result = new List < int > ( ); for ( int counter = 0 ; counter < ( end - start ); counter ++) { результат . Добавить ( начало + счетчик ); } вернуть результат ; } public LazyFactoryObject () { // пустой конструктор } public LazyObject GetLazyFactoryObject ( LazyObjectSize size ) { // да, я знаю, что это неграмотный и неточный LazyObject noGoodSomeOne ; // извлекает LazyObjectSize из списка через out, иначе создает его и добавляет в список if (! _LazyObjectList . TryGetValue ( size , out noGoodSomeOne )) { noGoodSomeOne = new LazyObject (); noGoodSomeOne . Размер = размер ; noGoodSomeOne . Результат = это . Результат ( размер ); _LazyObjectList . Добавить ( размер , noGoodSomeOne ); } return noGoodSomeOne ; } } }
C ++ [ править ]
Вот пример на C ++ .
#include <iostream>#include <карта>#include <строка>class Fruit { public : static Fruit * GetFruit ( const std :: string & type ); static void PrintCurrentTypes (); private : // Примечание: конструктор private заставляет использовать static | GetFruit |. Fruit ( const std :: string & type ) : type_ ( тип ) {} статические типы std :: map < std :: string , Fruit *> ; std :: string type_ ; };// статический std :: map < std :: string , Fruit *> Fruit :: types ;// Метод Lazy Factory, получает | Fruit | экземпляр, связанный с определенным // | типом |. При необходимости создает новые. Fruit * Fruit :: GetFruit ( const std :: string & type ) { auto [ it , вставлено ] = типы . emplace ( тип , nullptr ); если ( вставлено ) { это -> второй = новый фрукт ( тип ); } вернуть его -> второй ; }// Например, чтобы увидеть образец в действии. void Fruit :: PrintCurrentTypes () { std :: cout << "Количество созданных экземпляров =" << типов . size () << std :: endl ; for ( const auto & [ type , fruit ] : types ) { std :: cout << type << std :: endl ; } std ::cout << std :: endl ; }int main () { Fruit :: GetFruit ( "Банан" ); Fruit :: PrintCurrentTypes (); Fruit :: GetFruit ( «Яблоко» ); Fruit :: PrintCurrentTypes (); // Возвращает уже существующий экземпляр с первого раза | Fruit | с "Бананом" // было создано. Fruit :: GetFruit ( «Банан» ); Fruit :: PrintCurrentTypes (); }// ВЫХОД: // // Количество созданных экземпляров = 1 // Банан // // Количество созданных экземпляров = 2 // Apple // Банан // // Количество созданных экземпляров = 2 // Apple // Банан / /
Кристалл [ править ]
Тип частного получателя class Fruit : String @@ types = {} of String => Fruit def инициализировать ( @type ) конец def self . get_fruit_by_type ( тип : String ) @@ types [ type ] || = Fruit . новый ( тип ) конец def self . show_all помещает "Количество созданных экземпляров: # { @@ types . size } " @@ types . каждый делать | сорт , фруктовый | кладет конец " # { type } " кладет конец def self . размер @@ типы . размер конец конецФрукты . get_fruit_by_type ( "Банан" ) Фрукты . показать всеФрукты . get_fruit_by_type ( "Яблоко" ) Фрукты . показать всеФрукты . get_fruit_by_type ( "Банан" ) Фрукты . показать все
Выход:
Количество изготовленных экземпляров: 1БананКоличество изготовленных экземпляров: 2БананяблокоКоличество изготовленных экземпляров: 2Бананяблоко
Haxe [ править ]
class Fruit { private static var _instances = new Map < String , Fruit > (); общедоступное имя переменной (по умолчанию , null ): String ; публичная функция new ( name : String ) { this . name = name ; } общедоступная статическая функция getFruitByName ( name : String ): Fruit { if ( ! _instances . exists ( name )) { _instances . set ( название , новый Fruit ( название )); } return _instances . получить ( имя ); } общедоступная статическая функция printAllTypes () { trace ([ для ( ключа в _instances . keys ()) ключа ]); } }
Применение
class Test { public static function main () { var banana = Fruit . getFruitByName ( "Банан" ); var apple = Фрукты . getFruitByName ( "Яблоко" ); var banana2 = Фрукты . getFruitByName ( "Банан" ); след ( банан == банан2 ); // правда. тот же банан Фрукты . printAllTypes (); // ["Банан", "Яблоко"] } }
Java [ править ]
Этот пример не является потокобезопасным, см. Страницу обсуждения . Взгляните на примеры, приведенные в разделе Двойная проверка блокировки # Usage_in_Java . |
Вот пример на Java .
import java.util.HashMap ; import java.util.Map ; import java.util.Map.Entry ;public class Program { / ** * @param args * / public static void main ( String [] args ) { Fruit . getFruitByTypeName ( FruitType . банан ); Фрукты . showAll (); Фрукты . getFruitByTypeName ( FruitType . яблоко ); Фрукты . showAll (); Фрукты . getFruitByTypeName ( FruitType . банан ); Фрукты . показать все(); } }enum FruitType { нет , яблоко , банан , }class Fruit { частная статическая карта < FruitType , Fruit > types = new HashMap <> (); / ** * Использование частного конструктора для принудительного использования фабричного метода. * @param type * / private Fruit ( тип FruitType ) { } / ** * Метод Lazy Factory, получает экземпляр Fruit, связанный с * определенным типом. При необходимости создает новые экземпляры. * @param type Любой разрешенный тип фруктов, например APPLE * @return Экземпляр Fruit, связанный с этим типом. * / public static Fruit getFruitByTypeName ( тип FruitType ) { Fruit fruit ; // Это имеет проблемы с параллелизмом. Здесь чтение типов не синхронизируется, // поэтому типы .put и types.containsKey могут вызываться одновременно. // Не удивляйтесь, если данные будут повреждены. если ( ! типы . containsKey ( type )) { // Ленивая инициализация fruit = new Fruit ( type ); типы . положить ( вид , фрукт ); } else { // Хорошо, сейчас это доступно fruit = types . получить ( тип ); } вернуть фрукты ; } / ** * Метод Lazy Factory, получает экземпляр Fruit, связанный с * определенным типом. При необходимости создает новые экземпляры. Использует шаблон блокировки с двойной проверкой * для использования в средах с высокой степенью параллелизма. * @param type Любой разрешенный тип фруктов, например APPLE * @return Экземпляр Fruit, связанный с этим типом. * / public static Fruit getFruitByTypeNameHighConcurrentVersion ( FruitType type ) { if ( ! types . containsKey ( type )) { synchronized ( types ) { // Проверяем еще раз после получения блокировки, // чтобы убедиться, что экземпляр не был создан тем временем другим потоком if ( ! Types . ContainsKey ( type )) { // Типы ленивой инициализации . put ( тип , новый Fruit ( тип )); } } } возвращаемые типы . получить ( тип ); } / ** * Отображает все введенные фрукты. * / public static void showAll () { if ( types . size () > 0 ) { Система . из . println ( "Количество созданных экземпляров =" + типы . size ()); for ( Entry < FruitType , Fruit > entry : types . entrySet ()) { String fruit = entry . getKey (). toString (); Fruit = Персонаж . toUpperCase ( fruit . charAt ( 0 )) + fruit . подстрока ( 1 ); Система . из . println ( фрукты ); } Система . из . println (); } } }
Выход
Количество сделанных экземпляров = 1БананКоличество сделанных экземпляров = 2БананяблокоКоличество сделанных экземпляров = 2Бананяблоко
JavaScript [ править ]
Вот пример на JavaScript .
var Fruit = ( function () { var types = {}; function Fruit () {}; // подсчитываем собственные свойства в объекте function count ( obj ) { return Object . ключи ( obj ). длина ; } var _static = { getFruit : function ( type ) { if ( typeof types [ type ] == 'undefined' ) { types [ type ] = new Fruit ; } возвращаемые типы [ тип ]; }, printCurrentTypes : function () { console . log ( 'Количество созданных экземпляров:' + count ( типы)); for ( тип var в типах ) { console . журнал ( тип ); } } }; return _static ;}) ();Фрукты . getFruit ( 'Яблоко' ); Фрукты . printCurrentTypes (); Фрукты . getFruit ( 'Банан' ); Фрукты . printCurrentTypes (); Фрукты . getFruit ( 'Яблоко' ); Фрукты . printCurrentTypes ();
Выход
Количество изготовленных экземпляров: 1яблокоКоличество изготовленных экземпляров: 2яблокоБананКоличество изготовленных экземпляров: 2яблокоБанан
PHP [ править ]
Вот пример отложенной инициализации в PHP 7.4:
<? php header ( 'Content-Type: text / plain; charset = utf-8' );class Fruit { частная строка $ type ; частный статический массив $ types = array (); частная функция __construct ( строка $ type ) { $ this -> type = $ type ; } public static function getFruit ( string $ type ) { // Здесь происходит отложенная инициализация if ( ! isset ( self :: types [ $ type ])) { self :: types [ $ type ] = new Fruit ( $ type ); } return self :: types [ $ type ]; } общедоступная статическая функция printCurrentTypes () : void { echo 'Количество созданных экземпляров:' . count ( self :: types ) . " \ п " ; foreach ( array_keys ( self :: types ) as $ key ) { echo " $ key \ n " ; } эхо " \ п " ; } }Fruit :: getFruit ( «Яблоко» ); Fruit :: printCurrentTypes ();Fruit :: getFruit ( "Банан" ); Fruit :: printCurrentTypes ();Fruit :: getFruit ( «Яблоко» ); Fruit :: printCurrentTypes ();/ * ВЫВОД:Количество изготовленных экземпляров: 1 AppleКоличество изготовленных экземпляров: 2 яблочного бананаКоличество изготовленных экземпляров: 2 яблочного банана * /
Python [ править ]
Вот пример на Python .
class Fruit : def __init__ ( self , item : str ) -> None : self . item = предмет class Fruits : def __init__ ( self ) -> None : self . items = {} def get_fruit ( self , item : str ) -> Fruit : если предмет не в себе . предметы : самостоятельно . items [ item ] = Фрукты ( предмет ) вернуть себя . предметы [ предмет ]если __name__ == "__main__" : фрукты = фрукты () печать ( фрукты . get_fruit ( "Яблоко" )) печать ( фрукты . get_fruit ( "Лайм" ))
Руби [ править ]
Вот пример в Ruby ленивой инициализации токена аутентификации из удаленной службы, такой как Google. Способ кэширования @auth_token также является примером мемоизации .
требуется класс 'net / http' 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 и параметры определены позже в конце классаb = Blogger . новый б . instance_variable_get ( : @auth_token ) # возвращает ноль b . auth_token # возвращает токен b . instance_variable_get ( : @auth_token ) # возвращает токен
Scala [ править ]
Scala имеет встроенную поддержку отложенного запуска переменных. [2]
scala > val x = { println ( "Привет" ); 99 } Привет x : Int = 99 scala > lazy val y = { println ( "Привет !!" ); 31 } y : Int = < lazy > scala > y Привет !! res2 : Int = 31 scala > y res3 : Int = 31 год
Smalltalk [ править ]
Вот пример на Smalltalk типичного метода доступа для возврата значения переменной с использованием отложенной инициализации.
height ^ height ifNil: [ height : = 2.0 ] .
Альтернативный вариант - использовать метод инициализации, который запускается при создании объекта, а затем использовать более простой метод доступа для получения значения.
инициализировать высоту : = 2,0 высота ^ высота
Обратите внимание, что отложенная инициализация также может использоваться в не объектно-ориентированных языках .
Теоретическая информатика [ править ]
В области теоретической информатики , отложенная инициализация [3] (также называется ленивый массив ) представляет собой метод , чтобы проектировать структуры данных , которые могут работать с памятью , что не нужно быть инициализировано. В частности, предположим, что у нас есть доступ к таблице T из n неинициализированных ячеек памяти (пронумерованных от 1 до n ), и мы хотим назначить m ячеек этого массива, например, мы хотим назначить T [ k i ]: = v i для пары ( k 1 , v 1 ), ..., (k m , v m ), причем все k i различны. Техника отложенной инициализации позволяет нам сделать это всего за O ( m ) операций, вместо того, чтобы тратить O ( m + n ) операций на первоначальную инициализацию всех ячеек массива. Метод состоит в том, чтобы просто выделить таблицу V, в которой хранятся пары ( k i , v i ) в произвольном порядке, и записать для каждого i в ячейку T [ k i ] позицию в V, где хранится ключ k i , оставляя другие ячейкиT не инициализирован. Это можно использовать для обработки запросов следующим образом: когда мы ищем ячейку T [ k ] для некоторого k , мы можем проверить, находится ли k в диапазоне {1, ..., m }: если это не так, тогда T [ k ] не инициализирован. В противном случае мы проверяем V [ T [ k ]] и проверяем, что первая компонента этой пары равна k . Если это не так, то T [ k ] не инициализирован (и случайно попал в диапазон {1, ..., m }). В противном случае мы знаем, что T [k ] действительно является одной из инициализированных ячеек, а соответствующее значение является вторым компонентом пары.
См. Также [ править ]
- Двойная проверка блокировки
- Ленивая загрузка
- Шаблон прокси
- Шаблон синглтона
Ссылки [ править ]
- ^ «Ленивая инициализация - Шаблоны проектирования - Поваренная книга языка программирования Haxe» . 2018-01-11 . Проверено 9 ноября 2018 .
- ↑ Поллак, Дэвид (25 мая 2009 г.). Начиная с Scala . ISBN 9781430219897.
- ^ Морет, BME; Шапиро, HD (1991). Алгоритмы от P до NP, Том 1: Дизайн и эффективность . Бенджамин / Издательство Каммингс. С. 191–192. ISBN 0-8053-8008-6.
Внешние ссылки [ править ]
- Статья Филиппа Бишопа и Найджела Уоррена « Совет 67 для Java: Ленивое создание экземпляров - балансирование производительности и использования ресурсов»
- Примеры кода Java
- Используйте отложенную инициализацию для экономии ресурсов
- Описание из репозитория портлендских паттернов
- Ленивая инициализация служб сервера приложений
- Ленивое наследование в JavaScript
- Ленивое наследование в C #