A számítógépes programozásban, a lusta inicializáció programtervezési minta egy olyan taktika, amely szerint késleltetjük egy objektum létrehozását, vagy valamely számításigényes művelet elvégzését egészen addig, amíg az objektumra vagy a számítás eredményére először ténylegesen szükség lesz.
Ezt tipikusan egy változó elérésére szolgáló eljárás vagy egy objektum-tulajdonság (property) definíció bevezetésével valósítjuk meg, amelyben ellenőrizzük, hogy az elérni (használni) kívánt objektum példány (vagy számítási eredmény) létezik-e már. Ha nem létezik, egy új példány kerül létrehozásra és tárolásra a kapcsolódó változóban, és ennek értéke kerül a hívónak (az adatra hivatkozó kódrészletnek) visszaadásra „éppen a kellő időben” (Just In Time) módon.
Ezzel a viselkedéssel az objektum létrehozását „elhalasztják” az első használatig, amely bizonyos körülmények között (pl. az objektum ritkább használata esetén), csökkenti a rendszer válaszidejét és gyorsítja az indítást azáltal, hogy elkerüli a nagyméretű objektumok előzetes létrehozását és memóriafoglalását. (Megjegyezzük, hogy akár ellenkező hatást okozhat az általános teljesítményben, ha a késleltetett objektum létrehozás előnyeit „elhasználjuk” a rendszer indító/előkészítő fázisa során.)
Több szálon futó kód esetén, a lusta inicializációval használt objektumokhoz való hozzáférést szinkronizálni kell (kölcsönös kizárás/mutex), a versenyhelyzet (race condition) elkerülése érdekében.
A késleltetett kiértékelés jól mutatja az alapötlet általános megvalósítását. Az erősen imperatív programozási nyelvekben ez a minta rejtett veszélyeket hordoz, csakúgy mint bármely programozási szokás amely megosztott állapotokra támaszkodik.
A „lusta gyár”
A programtervezési minták nézőpontjából vizsgálva, a lusta inicializáció gyakran együttesen használt a gyártó metódus programtervezési mintával. Ez a megoldás három elgondolást kombinál: Egy gyártó metódust használunk, hogy példányokat kapjunk egy osztályból (gyártó metódus programtervezési minta) A példányokat egy asszociatív tömbben tároljuk (létrehozási paraméterek/létrehozott példány), így amikor legközelebb „ugyanazokkal” a paraméterekkel kérnek példányt a gyártól, „ugyanazt” a példányt adja majd vissza, ami az adott paraméterekkel már létrehozásra került (többke programtervezési minta) Lusta inicializációt használunk az objektumok létrehozására, azok csak akkor jönnek létre, amikor először kérik őket a gyártól
Példakódok
C C-ben a lusta inicializáció egyetlen függvényben vagy egyetlen forrásfájlban kerül megvalósításra, statikus változók használatával.
Egy függvényben: #include<string.h> #include<stdlib.h> #include<stddef.h> #include<stdio.h>
struct fruit { char *name; struct fruit *next; int number; /* Egyéb adattagok */
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; }
/* Példa kód */
int main(int argc, char *argv[]) { int i; struct fruit *f; if (argc<2) { fprintf(stderr, "Használat: fruits gyümölcs-név [...]\n"); exit(1);
for (i=1; i<argc; i++) { if ((f = get_fruit(argv[i]))) { printf("Gyümölcs %s: száma %d\n", argv[i], f->number);
return 0; } Egyetlen forrásfájl használata a fentiek helyett lehetővé teszi, hogy az állapotok megosztásra kerüljenek több függvény között úgy, hogy a nem kapcsolódó függvények elől továbbra is el vannak rejtve.
fruit.h: #ifndef _FRUIT_INCLUDED_ #define _FRUIT_INCLUDED_
struct fruit { char *name; struct fruit *next; int number; /* Egyéb adattagok */
struct fruit *get_fruit(char *name); void print_fruit_list(FILE *file);
#endif /* _FRUIT_INCLUDED_ */
fruit.c: #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); }
main.c: #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, "Használat: fruits gyümölcs-név [...]\n"); exit(1);
for (i=1; i<argc; i++) { if ((f = get_fruit(argv[i]))) { printf("Gyümölcs %s: száma %d\n", argv[i], f->number);
printf("A következő gyümölcsök (fruits) generálódtak:\n"); print_fruit_list(stdout); return 0; }
C# A .NET 4.0-ban a Microsoft elérhetővé tett egy Lazy osztályt, amely lusta (késői) betöltéshez használható. Az alábbi néhány sor mintakód a Fruit osztály lusta betöltését mutatja: Lazy<Fruit> lazyFruit = new Lazy<Fruit>(); Fruit fruit = lazyFruit.Value;
Az alábbi egy minta kód C# programozási nyelven.
A Fruit osztály maga nem csinál semmit a példában, a _typesDictionary osztály-változó egy szótár/asszociatív tömb amiben a Fruit példányokat tároljuk a typeName (típusnév) alapján.
using System; using System.Collections; using System.Collections.Generic;
public class Fruit { private string _typeName; private static Dictionary<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))
// Lusta inicializáció fruit = new Fruit(type);
_typesDictionary.Add(type, fruit);
return fruit;
public static void ShowAll()
if (_typesDictionary.Count > 0)
Console.WriteLine("Az elkészített példányok száma = {0}", _typesDictionary.Count);
foreach (KeyValuePair<string, Fruit> kvp in _typesDictionary)
Console.WriteLine(kvp.Key);
Console.WriteLine();
public Fruit()
// szükséges a mintakód lefordításához
class Program { static void Main(string[] args)
Fruit.GetFruitByTypeName("Banana"); Fruit.ShowAll();
Fruit.GetFruitByTypeName("Apple"); Fruit.ShowAll();
// az előzőleg már létrehozott példányt adja vissza // ami a "Banana" paraméterrel való első hívásnál létrejött Fruit.GetFruitByTypeName("Banana"); Fruit.ShowAll();
Console.ReadLine();
Az alábbiakban egy meglehetősen lényegre törő példáját mutatjuk be a lusta inicializáció programtervezési mintának azzal, hogy a példa enumerációt (felsorolással definiált típust) használ alkalmazott típusként. //IVSR: LazyInitialization design pattern namespace IVSR.DesignPatterns.LazyInitialization { public class LazyFactoryObject
// az elemek belsőleg használt gyűjteménye. // az IDictionary típus gondoskodik róla, hogy az elemek egyediek legyenek private IDictionary<LazyObjectType, LazyObject> _LazyObjectList = new Dictionary<LazyObjectType, LazyObject>();
// felsorolási típus a kívánt típusnevek megadásához. // így nem szükséges string paraméterátadást, // és lehetséges a fordításkori típusellenőrzés public enum LazyObjectType
None, Small, Big, Bigger, Huge
// egy általános típus a létrehozandó objektumoknak public struct LazyObject
public LazyObjectType Name; public IList<int> Result;
// egy felsorolási típus elemet vesz át, és létrehoz egy // 'drága' (időigényes, számításigényes stb.) listát private IList<int> Result(LazyObjectType name)
IList<int> result = null;
switch (name)
case LazyObjectType.Small: result = CreateSomeExpensiveList(1, 100); break; case LazyObjectType.Big: result = CreateSomeExpensiveList(1, 1000); break; case LazyObjectType.Bigger: result = CreateSomeExpensiveList(1, 10000); break; case LazyObjectType.Huge: result = CreateSomeExpensiveList(1, 100000); break; case LazyObjectType.None: result = null; break; default: result = null; break;
return result;
// nem lenne 'drága' (számításigényes) létrehozni az elemet // de hogy érthető legyen a lényeg, késleltetjük néhány // számításigényes objektum létrehozását addig, // amíg szükség nem lesz rájuk 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()
// üres konstruktor
public LazyObject GetLazyFactoryObject(LazyObjectType name)
LazyObject noGoodSomeOne;
// 'előveszi' a LazyObjectType elemet a listáról ha van már ott, // egyébként (ha nincs) létrehoz egyet és a listára (is) teszi if (!_LazyObjectList.TryGetValue(name, out noGoodSomeOne))
noGoodSomeOne = new LazyObject(); noGoodSomeOne.Name = name; noGoodSomeOne.Result = this.Result(name);
_LazyObjectList.Add(name, noGoodSomeOne);
return noGoodSomeOne;
C++ Az alábbiakban egy C++ példa.
#include <iostream> #include <string> #include <map>
using namespace std;
class Fruit { public: static Fruit* getFruit(const string& type); static void printCurrentTypes();
private: static map<string,Fruit*> types; string type;
// megjegyzés: a privát konstruktor kikényszeríti, hogy a statikus // getFruit() függvénnyel kelljen objektum példányt létrehozni Fruit(const string& t) : type( t ) {}
// szükséges definíció a statikus tag-változó használatához map<string,Fruit*> Fruit::types;
/* * Lusta gyártó metódus, azt a 'Fruit' példányt veszi elő, amire a paraméterként * átadott névvel hivatkoztak. Létrehoz egy új példányt, ha szükséges. * előfeltétel: a 'type' paraméter valamilyen gyümölcs név legyen pl. "alma" * utófeltétel: visszaadja a 'Fruit' példányt, amire a névvel hivatkoztunk */ Fruit* Fruit::getFruit(const string& type) { // próbálunk keresni egy létező példányt; ha nem találunk, // az std::map függvény 'types.end()' értéket ad vissza map<string,Fruit*>::iterator it = types.find(type);
Fruit *f; if (it == types.end()) { // ha nem találtunk példányt a kért típusból, hozzunk létre egyet f = new Fruit(type); // lusta inicializációs rész // hozzáadjuk az újonnan létrehozott 'Fruit' példányt // a 'types' asszociatív tömbhöz, hogy később megtaláljuk types[type] = f; } else { // ha már van ilyen példányunk // a visszaadott érték a megtalált 'Fruit' példány lesz f = it->second;
return f; }
/* * Példaként lássuk a minta használatát */ void Fruit::printCurrentTypes() { if (!types.empty()) { cout << "A létrehozott példányok száma = " << types.size() << endl; for (map<string,Fruit*>::iterator iter = types.begin(); iter != types.end(); ++iter) { cout << (*iter).first << endl;
cout << endl;
int main(void) { Fruit::getFruit("Banana"); Fruit::printCurrentTypes();
Fruit::getFruit("Apple"); Fruit::printCurrentTypes();
// az előzőleg már létrehozott példányt adja vissza // ami a "Banana" paraméterrel való első hívásnál létrejött Fruit::getFruit("Banana"); Fruit::printCurrentTypes();
return 0; }
/* KIMENET: A létrehozott példányok száma = 1 Banana
A létrehozott példányok száma = 2 Apple Banana
A létrehozott példányok száma = 2 Apple Banana */
Java Az alábbiakban egy Java példát mutatunk be.
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<>();
/** * A privát konstruktor kikényszeríti a gyártó metódus használatát. * @param type */