Lusta inicializáció programtervezési minta
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”
szerkesztésA 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
szerkesztésC-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#
szerkesztésA .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++
szerkesztésAz 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
szerkesztésAz 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
*/
private Fruit(FruitType type) {
}
/**
* Lusta gyártó metódus, azt a 'Fruit' példányt veszi elő, amire a paraméterrel
* hivatkoztak. Létrehoz egy új példányt, ha szükséges.
* @param type Bármilyen a típusnál megengedett gyümölcs név, pl. APPLE
* @return A 'Fruit' példány, amire a típus-elem névvel hivatkoztunk
*/
public static Fruit getFruitByTypeName(FruitType type) {
Fruit fruit;
// Ennek a programrésznek konkurencia problémái lehetnek.
// Az alábbiakban a 'types'-hoz való hozzáférés nem szinkronizált,
// így a 'types.put' és a 'types.containsKey' egyszerre is meghívásra kerülhet.
// Nem kell meglepődni, ha az adatok sérülnek... :)
if (!types.containsKey(type)) {
// Lusta inicializáció
fruit = new Fruit(type);
types.put(type, fruit);
} else {
// OK, már elérhető
fruit = types.get(type);
}
return fruit;
}
/**
* Lusta gyártó metódus, azt a 'Fruit' példányt veszi elő, amire a paraméterrel
* hivatkoztak. Létrehoz egy új példányt, ha szükséges. Duplán ellenőrzött lockolás
* mintát használ a konkurens hozzáférés biztonságossá tételére.
* @param type Bármilyen a típusnál megengedett gyümölcs név, pl. APPLE
* @return A 'Fruit' példány, amire a típus-elem névvel hivatkoztunk
*/
public static Fruit getFruitByTypeNameHighConcurrentVersion(FruitType type) {
if (!types.containsKey(type)) {
synchronized (types) {
// Újra ellenőrizzük a 'type' létezését, miután lockoltuk a 'types'-t
// hogy biztosak legyünk benne, hogy nem került időközben létrehozásra
// belőle példány egy másik programszálon
if (!types.containsKey(type)) {
// Lusta inicializáció
types.put(type, new Fruit(type));
}
}
}
return types.get(type);
}
/**
* Kiírja az összes létrehozott 'Fruit' példányt.
*/
public static void showAll() {
if (types.size() > 0) {
System.out.println("A létrehozott példányok száma = " + 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();
}
}
}
Kimenet
A létrehozott példányok száma = 1 Banana A létrehozott példányok száma = 2 Banana Apple A létrehozott példányok száma = 2 Banana Apple
JavaScript
szerkesztésAz alábbiakban egy JavaScript példa.
var Fruit = (function() {
var types = {};
function Fruit() {};
// visszaadja a paraméterként kapott objektum
// tulajdonságainak(property) számát
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('A létrehozott példányok száma: ' + 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();
Kimenet
A létrehozott példányok száma: 1 Apple A létrehozott példányok száma: 2 Apple Banana A létrehozott példányok száma: 2 Apple Banana
PHP
szerkesztésAz alábbiakban egy példa a lusta inicializációra PHP 5-ben:
<?php
header('Content-type:text/plain; charset=utf-8');
class Fruit {
private $type;
private static $types = array();
private function __construct($type) {
$this->type = $type;
}
public static function getFruit($type) {
// A lusta inicializáció itt történik
if (!isset(self::$types[$type])) {
self::$types[$type] = new Fruit($type);
}
return self::$types[$type];
}
public static function printCurrentTypes() {
echo 'A létrehozott példányok száma: ' . 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();
/*
KIMENET:
A létrehozott példányok száma: 1
Apple
A létrehozott példányok száma: 2
Apple
Banana
A létrehozott példányok száma: 2
Apple
Banana
*/
?>
Python
szerkesztésAz alábbiakban egy példa Python programozási nyelven (2.x verziójú Python).
class Fruit:
def __init__(self, sort):
self.sort = sort
class Fruits:
def __init__(self):
self.sorts = {}
def get_fruit(self, sort):
if sort not in self.sorts:
self.sorts[sort] = Fruit(sort)
return self.sorts[sort]
if __name__ == '__main__':
fruits = Fruits()
print fruits.get_fruit('Apple')
print fruits.get_fruit('Lime')
Ruby
szerkesztésAz alábbiakban egy példa Ruby nyelven, egy távoli szolgáltatástól származó (mint pl. a Google-é) autentikációs token lusta inicializálására.
require 'net/http'
class Blogger
def auth_token
@auth_token ||=
(res = Net::HTTP.post_form(uri, params)) &&
get_token_from_http_response(res)
end
# a 'get_token_from_http_response', az 'uri' és a 'params' később vannak definiálva az osztályban
end
b = Blogger.new
b.instance_variable_get(:@auth_token) # nil értéket ad vissza
b.auth_token # a tokent adja vissza
b.instance_variable_get(:@auth_token) # a tokent adja vissza
Smalltalk
szerkesztésAz alábbiakban egy Smalltalk nyelvű példa, egy tipikus változó-elérést biztosító metódusra, amely a változó értékét lusta inicializáció használatával adja vissza.
height
^height ifNil: [height := 2.0].
A fentiek 'nem-lusta' alternatívája egy inicializációs metódus használata, ami már akkor lefut, amikor az objektum létrejön. Ebben az esetben egy egyszerűbb változó-elérésre szolgáló metódus használható az érték kiolvasására.
initialize
height := 2.0
height
^height
Megjegyezzük, hogy a lusta inicializáció a nem objektumorientált nyelvekben is használható.
Scala
szerkesztésA Scala programozási nyelvnek beépített támogatása van a lusta változó inicializációhoz.[1]
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
Jegyzetek
szerkesztésKülső hivatkozások (angolul)
szerkesztés- Article "Java Tip 67: Lazy instantiation - Balancing performance and resource usage" by Philip Bishop and Nigel Warren
- Java code examples
- Use Lazy Initialization to Conserve Resources
- Description from the Portland Pattern Repository
- Lazy Initialization of Application Server Services
- Lazy Inheritance in JavaScript
- Lazy Inheritance in C#
Fordítás
szerkesztésEz a szócikk részben vagy egészben a Lazy initialization című angol Wikipédia-szócikk ezen változatának fordításán alapul. Az eredeti cikk szerkesztőit annak laptörténete sorolja fel. Ez a jelzés csupán a megfogalmazás eredetét és a szerzői jogokat jelzi, nem szolgál a cikkben szereplő információk forrásmegjelöléseként.