Ленивая инициализация
В компьютерном программировании ленивая инициализация — это тактика отсрочки создания объекта , вычисления значения или какого-либо другого дорогостоящего процесса до тех пор, пока он не понадобится в первый раз. Это своего рода ленивая оценка , которая относится конкретно к созданию объектов или других ресурсов.
Обычно это достигается путем дополнения метода доступа (или метода получения свойства) для проверки того, был ли уже инициализирован закрытый член, действующий как кэш. Если да, то он немедленно возвращается. В противном случае создается новый экземпляр, помещается в переменную-член и возвращается вызывающему объекту как раз вовремя для его первого использования.
Если объекты имеют свойства, которые редко используются, это может повысить скорость запуска. Средняя производительность программы может быть немного хуже с точки зрения памяти (для условных переменных) и циклов выполнения (для их проверки), но влияние создания экземпляра объекта распределяется во времени («амортизируется»), а не концентрируется на этапе запуска системы, и, таким образом, среднее время ответа может быть значительно улучшено.
В многопоточном коде доступ к лениво инициализированным объектам/состоянию должен быть синхронизирован для защиты от условий гонки .
«ленивая фабрика»
[ редактировать ]В представлении шаблона проектирования программного обеспечения ленивая инициализация часто используется вместе с шаблоном фабричного метода . Это объединяет три идеи:
- Использование фабричного метода для создания экземпляров класса ( шаблон фабричного метода )
- Сохранение экземпляров на карте и возврат одного и того же экземпляра при каждом запросе экземпляра с одинаковыми параметрами ( многотонный шаблон ).
- Использование ленивой инициализации для создания экземпляра объекта при первом запросе (шаблон ленивой инициализации).
Примеры
[ редактировать ]Экшнскрипт 3
[ редактировать ]Ниже приведен пример класса с отложенной инициализацией, реализованной в 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)
{
// iterates through each value
trace(fruit.typeName);
}
}
}
}
Основное использование:
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();
}
}
}
С
[ редактировать ]В C ленивые вычисления обычно реализуются внутри одной функции или одного исходного файла с использованием статических переменных .
В функции:
#include <string.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdio.h>
struct fruit {
char *name;
struct fruit *next;
int number;
/* Other members */
};
struct fruit *get_fruit(char *name) {
static struct fruit *fruit_list;
static int seq;
struct fruit *f;
for (f = fruit_list; f; f = f->next)
if (0 == strcmp(name, f->name))
return f;
if (!(f = malloc(sizeof(struct fruit))))
return NULL;
if (!(f->name = strdup(name))) {
free(f);
return NULL;
}
f->number = ++seq;
f->next = fruit_list;
fruit_list = f;
return f;
}
/* Example code */
int main(int argc, char *argv[]) {
int i;
struct fruit *f;
if (argc < 2) {
fprintf(stderr, "Usage: fruits fruit-name [...]\n");
exit(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;
}
Вместо этого использование одного исходного файла позволяет разделить состояние между несколькими функциями, при этом скрывая его от несвязанных функций.
фрукты.ч:
#ifndef _FRUIT_INCLUDED_
#define _FRUIT_INCLUDED_
struct fruit {
char *name;
struct fruit *next;
int number;
/* Other members */
};
struct fruit *get_fruit(char *name);
void print_fruit_list(FILE *file);
#endif /* _FRUIT_INCLUDED_ */
фрукты.с:
#include <string.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdio.h>
#include "fruit.h"
static struct fruit *fruit_list;
static 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;
if (!(f = malloc(sizeof(struct fruit))))
return NULL;
if (!(f->name = strdup(name))) {
free(f);
return NULL;
}
f->number = ++seq;
f->next = fruit_list;
fruit_list = f;
return f;
}
void print_fruit_list(FILE *file) {
struct fruit *f;
for (f = fruit_list; f; f = f->next)
fprintf(file, "%4d %s\n", f->number, f->name);
}
основной.с:
#include <stdlib.h>
#include <stdio.h>
#include "fruit.h"
int main(int argc, char *argv[]) {
int i;
struct fruit *f;
if (argc < 2) {
fprintf(stderr, "Usage: fruits fruit-name [...]\n");
exit(1);
}
for (i = 1; i < argc; i++) {
if ((f = get_fruit(argv[i]))) {
printf("Fruit %s: number %d\n", argv[i], f->number);
}
}
printf("The following fruits have been generated:\n");
print_fruit_list(stdout);
return 0;
}
С#
[ редактировать ]В .NET Framework 4.0 Microsoft включила Lazy
класс, который можно использовать для отложенной загрузки.
Ниже приведен фиктивный код, который выполняет ленивую загрузку класса. Fruit
var lazyFruit = new Lazy<Fruit>();
Fruit fruit = lazyFruit.Value;
Вот фиктивный пример на C# .
The Fruit
сам класс здесь ничего не делает. Переменная класса _typesDictionary
это словарь/карта, используемая для хранения Fruit
экземпляры по typeName
.
using System;
using System.Collections;
using System.Collections.Generic;
public class Fruit
{
private string _typeName;
private static IDictionary<string, Fruit> _typesDictionary = new Dictionary<string, Fruit>();
private Fruit(string typeName)
{
this._typeName = typeName;
}
public static Fruit GetFruitByTypeName(string type)
{
Fruit fruit;
if (!_typesDictionary.TryGetValue(type, out fruit))
{
// Lazy initialization
fruit = new Fruit(type);
_typesDictionary.Add(type, fruit);
}
return fruit;
}
public static void ShowAll()
{
if (_typesDictionary.Count > 0)
{
Console.WriteLine("Number of instances made = {0}", _typesDictionary.Count);
foreach (KeyValuePair<string, Fruit> kvp in _typesDictionary)
{
Console.WriteLine(kvp.Key);
}
Console.WriteLine();
}
}
public Fruit()
{
// required so the sample compiles
}
}
class Program
{
static void Main(string[] args)
{
Fruit.GetFruitByTypeName("Banana");
Fruit.ShowAll();
Fruit.GetFruitByTypeName("Apple");
Fruit.ShowAll();
// returns pre-existing instance from first
// time Fruit with "Banana" was created
Fruit.GetFruitByTypeName("Banana");
Fruit.ShowAll();
Console.ReadLine();
}
}
Достаточно простой пример шаблона проектирования «Отложенная инициализация» с «заполнением пробелов», за исключением того, что здесь используется перечисление для типа
namespace DesignPatterns.LazyInitialization;
public class LazyFactoryObject
{
// internal collection of items
// IDictionary makes sure they are unique
private IDictionary<LazyObjectSize, LazyObject> _LazyObjectList =
new Dictionary<LazyObjectSize, LazyObject>();
// enum for passing name of size required
// avoids passing strings and is part of LazyObject ahead
public enum LazyObjectSize
{
None,
Small,
Big,
Bigger,
Huge
}
// standard type of object that will be constructed
public struct LazyObject
{
public LazyObjectSize Size;
public IList<int> Result;
}
// takes size and create 'expensive' list
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;
}
return result;
}
// not an expensive item to create, but you get the point
// delays creation of some expensive object until needed
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);
}
return result;
}
public LazyFactoryObject()
{
// empty constructor
}
public LazyObject GetLazyFactoryObject(LazyObjectSize size)
{
// yes, i know it is illiterate and inaccurate
LazyObject noGoodSomeOne;
// retrieves LazyObjectSize from list via out, else creates one and adds it to list
if (!_LazyObjectList.TryGetValue(size, out noGoodSomeOne))
{
noGoodSomeOne = new LazyObject();
noGoodSomeOne.Size = size;
noGoodSomeOne.Result = this.Result(size);
_LazyObjectList.Add(size, noGoodSomeOne);
}
return noGoodSomeOne;
}
}
С++
[ редактировать ]Вот пример на C++ .
#include <iostream>
#include <map>
#include <string>
class Fruit {
public:
static Fruit* GetFruit(const std::string& type);
static void PrintCurrentTypes();
private:
// Note: constructor private forcing one to use static |GetFruit|.
Fruit(const std::string& type) : type_(type) {}
static std::map<std::string, Fruit*> types;
std::string type_;
};
// static
std::map<std::string, Fruit*> Fruit::types;
// Lazy Factory method, gets the |Fruit| instance associated with a certain
// |type|. Creates new ones as needed.
Fruit* Fruit::GetFruit(const std::string& type) {
auto [it, inserted] = types.emplace(type, nullptr);
if (inserted) {
it->second = new Fruit(type);
}
return it->second;
}
// For example purposes to see pattern in action.
void Fruit::PrintCurrentTypes() {
std::cout << "Number of instances made = " << types.size() << std::endl;
for (const auto& [type, fruit] : types) {
std::cout << type << std::endl;
}
std::cout << std::endl;
}
int main() {
Fruit::GetFruit("Banana");
Fruit::PrintCurrentTypes();
Fruit::GetFruit("Apple");
Fruit::PrintCurrentTypes();
// Returns pre-existing instance from first time |Fruit| with "Banana" was
// created.
Fruit::GetFruit("Banana");
Fruit::PrintCurrentTypes();
}
// OUTPUT:
//
// Number of instances made = 1
// Banana
//
// Number of instances made = 2
// Apple
// Banana
//
// Number of instances made = 2
// Apple
// Banana
//
Кристалл
[ редактировать ]class Fruit
private getter type : String
@@types = {} of String => Fruit
def initialize(@type)
end
def self.get_fruit_by_type(type : String)
@@types[type] ||= Fruit.new(type)
end
def self.show_all
puts "Number of instances made: #{@@types.size}"
@@types.each do |type, fruit|
puts "#{type}"
end
puts
end
def self.size
@@types.size
end
end
Fruit.get_fruit_by_type("Banana")
Fruit.show_all
Fruit.get_fruit_by_type("Apple")
Fruit.show_all
Fruit.get_fruit_by_type("Banana")
Fruit.show_all
Выход:
Number of instances made: 1 Banana Number of instances made: 2 Banana Apple Number of instances made: 2 Banana Apple
Смешанный
[ редактировать ]class Fruit {
private static var _instances = new Map<String, Fruit>();
public var name(default, null):String;
public function new(name:String) {
this.name = name;
}
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]);
}
}
Использование
class Test {
public static function main () {
var banana = Fruit.getFruitByName("Banana");
var apple = Fruit.getFruitByName("Apple");
var banana2 = Fruit.getFruitByName("Banana");
trace(banana == banana2); // true. same banana
Fruit.printAllTypes(); // ["Banana","Apple"]
}
}
Ява
[ редактировать ]Этот пример не является потокобезопасным, см. страницу обсуждения . Вместо этого взгляните на примеры, представленные в разделе Блокировка с двойной проверкой#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.banana);
Fruit.showAll();
Fruit.getFruitByTypeName(FruitType.apple);
Fruit.showAll();
Fruit.getFruitByTypeName(FruitType.banana);
Fruit.showAll();
}
}
enum FruitType {
none,
apple,
banana,
}
class Fruit {
private static Map<FruitType, Fruit> types = new HashMap<>();
/**
* Using a private constructor to force the use of the factory method.
* @param type
*/
private Fruit(FruitType type) {
}
/**
* Lazy Factory method, gets the Fruit instance associated with a certain
* type. Instantiates new ones as needed.
* @param type Any allowed fruit type, e.g. APPLE
* @return The Fruit instance associated with that type.
*/
public static Fruit getFruitByTypeName(FruitType type) {
Fruit fruit;
// This has concurrency issues. Here the read to types is not synchronized,
// so types.put and types.containsKey might be called at the same time.
// Don't be surprised if the data is corrupted.
if (!types.containsKey(type)) {
// Lazy initialisation
fruit = new Fruit(type);
types.put(type, fruit);
} else {
// OK, it's available currently
fruit = types.get(type);
}
return fruit;
}
/**
* Lazy Factory method, gets the Fruit instance associated with a certain
* type. Instantiates new ones as needed. Uses double-checked locking
* pattern for using in highly concurrent environments.
* @param type Any allowed fruit type, e.g. APPLE
* @return The Fruit instance associated with that type.
*/
public static Fruit getFruitByTypeNameHighConcurrentVersion(FruitType type) {
if (!types.containsKey(type)) {
synchronized (types) {
// Check again, after having acquired the lock to make sure
// the instance was not created meanwhile by another thread
if (!types.containsKey(type)) {
// Lazy initialisation
types.put(type, new Fruit(type));
}
}
}
return types.get(type);
}
/**
* Displays all entered fruits.
*/
public static void showAll() {
if (types.size() > 0) {
System.out.println("Number of instances made = " + 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();
}
}
}
Выход
Number of instances made = 1 Banana Number of instances made = 2 Banana Apple Number of instances made = 2 Banana Apple
JavaScript
[ редактировать ]Вот пример на JavaScript .
var Fruit = (function() {
var types = {};
function Fruit() {};
// count own properties in object
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('Number of instances made: ' + count(types));
for (var type in types) {
console.log(type);
}
}
};
return _static;
})();
Fruit.getFruit('Apple');
Fruit.printCurrentTypes();
Fruit.getFruit('Banana');
Fruit.printCurrentTypes();
Fruit.getFruit('Apple');
Fruit.printCurrentTypes();
Выход
Number of instances made: 1 Apple Number of instances made: 2 Apple Banana Number of instances made: 2 Apple Banana
PHP
[ редактировать ]Вот пример ленивой инициализации в PHP 7.4:
<?php
header('Content-Type: text/plain; charset=utf-8');
class Fruit
{
private string $type;
private static array $types = array();
private function __construct(string $type)
{
$this->type = $type;
}
public static function getFruit(string $type)
{
// Lazy initialization takes place here
if (!isset(self::$types[$type])) {
self::$types[$type] = new Fruit($type);
}
return self::$types[$type];
}
public static function printCurrentTypes(): void
{
echo 'Number of instances made: ' . count(self::$types) . "\n";
foreach (array_keys(self::$types) as $key) {
echo "$key\n";
}
echo "\n";
}
}
Fruit::getFruit('Apple');
Fruit::printCurrentTypes();
Fruit::getFruit('Banana');
Fruit::printCurrentTypes();
Fruit::getFruit('Apple');
Fruit::printCurrentTypes();
/*
OUTPUT:
Number of instances made: 1
Apple
Number of instances made: 2
Apple
Banana
Number of instances made: 2
Apple
Banana
*/
Питон
[ редактировать ]Вот пример на Python .
class Fruit:
def __init__(self, item: str):
self.item = item
class FruitCollection:
def __init__(self):
self.items = {}
def get_fruit(self, item: str) -> Fruit:
if item not in self.items:
self.items[item] = Fruit(item)
return self.items[item]
if __name__ == "__main__":
fruits = FruitCollection()
print(fruits.get_fruit("Apple"))
print(fruits.get_fruit("Lime"))
Руби
[ редактировать ]Вот пример на Ruby ленивой инициализации токена аутентификации из удаленной службы, такой как Google. Способ кэширования @auth_token также является примером мемоизации .
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 and params are defined later in the class
end
b = Blogger.new
b.instance_variable_get(:@auth_token) # returns nil
b.auth_token # returns token
b.instance_variable_get(:@auth_token) # returns token
Скала
[ редактировать ]Scala имеет встроенную поддержку ленивой инициализации переменных. [2]
scala> val x = { println("Hello"); 99 }
Hello
x: Int = 99
scala> lazy val y = { println("Hello!!"); 31 }
y: Int = <lazy>
scala> y
Hello!!
res2: Int = 31
scala> y
res3: Int = 31
Смолток
[ редактировать ]Вот пример на Smalltalk типичного метода доступа для возврата значения переменной с использованием ленивой инициализации.
height
^height ifNil: [height := 2.0].
«Неленивая» альтернатива — использовать метод инициализации, который запускается при создании объекта, а затем использовать более простой метод доступа для получения значения.
initialize
height := 2.0
height
^height
Обратите внимание, что ленивая инициализация также может использоваться в необъектно -ориентированных языках .
Теоретическая информатика
[ редактировать ]В области информатики теоретической ленивая инициализация [3] (также называемый ленивым массивом ) — это метод проектирования структур данных , которые могут работать с памятью, не требующей инициализации. В частности, предположим, что у нас есть доступ к таблице T из n неинициализированных ячеек памяти (с номерами от до n ) и мы хотим назначить m ячеек этого массива, например, мы хотим назначить T [ k i ] := vi 1 для пары ( k 1 , v 1 ), ..., ( km ) , , v m где все k i различны. Техника ленивой инициализации позволяет нам сделать это всего за O( m ) операций, вместо того, чтобы тратить O( m + n ) операций на первую инициализацию всех ячеек массива. Техника состоит в том, чтобы просто выделить таблицу V, пары ( ki , хранящую vi ) в некотором произвольном порядке, и записать для каждого i в ячейке T [ ki ] позицию в V ключ k i , где хранится , оставив остальные ячейки T неинициализируются. Это можно использовать для обработки запросов следующим образом: когда мы ищем в ячейке T [ k ] некоторый k , мы можем проверить, находится ли k в диапазоне {1, ..., m }: если это не так, то T [ k ] не инициализирован. В противном случае мы проверяем V [ T [ k ]] и проверяем, что первый компонент этой пары равен к . Если это не так, то T [ k ] не инициализирован (и просто случайно попал в диапазон {1,..., m }). В противном случае мы знаем, что T [ k ] действительно является одной из инициализированных ячеек, а соответствующее значение — вторым компонентом пары.
См. также
[ редактировать ]Ссылки
[ редактировать ]- ^ «Ленивая инициализация — Шаблоны проектирования — Рецепты языка программирования Haxe» . 11 января 2018 г. Проверено 9 ноября 2018 г.
- ^ Поллак, Дэвид (25 мая 2009 г.). Начало Скалы . ISBN 9781430219897 .
- ^ Море, BME; Шапиро, HD (1991). Алгоритмы от P до NP, Том 1: Проектирование и эффективность . Издательская компания Бенджамина/Каммингса. стр. 191–192. ISBN 0-8053-8008-6 .
Внешние ссылки
[ редактировать ]- Статья « Совет по Java 67: Ленивое создание экземпляров — баланс производительности и использования ресурсов» Филиппа Бишопа и Найджела Уоррена
- Примеры кода Java
- Используйте ленивую инициализацию для экономии ресурсов
- Описание из репозитория шаблонов Портленда.
- Ленивая инициализация служб сервера приложений
- Ленивое наследование в JavaScript
- Ленивое наследование в C#