/ janturon.cz / Od kodéra k analytikovi / Odlehčení a Fond

Odlehčení (Flyweight) a Fond (Object Pool)

Odlehčení

Tento vzor použijeme, tvoříme-li více objektů téže instance pouze ke čtení. Potom není důvod znovu vytvářet již existující objekt. Hodí se při práci s velkým množstvím objektů nebo s objekty spotřebovávající množství zdrojů při tvorbě (např. navazování spojení se serverem).

Příklad

Načtěme si knihovny

#include <iostream> #include <string> #include <map> #include "property.h" using namespace std;

a předstírejme, že objekt HeavyMetal je opravdu obtížný na vytvoření:

struct HeavyMetal { property<string> contents; HeavyMetal() { } HeavyMetal(const HeavyMetal&) { } HeavyMetal(string contents) : contents(contents) { cout << "Creating " << contents; cout << ", consuming lots of resources." << endl; } };

Potom nám zdroje ušetří odlehčení, které zajistí nevytvoření nové instance již existujícího objektu:

class Flyweight { typedef map<string,HeavyMetal> data; data storage; public: string Get(string key) { data::iterator it = storage.find(key); if(it==storage.end()) storage[key] = HeavyMetal(key); return "We have " + storage[key].contents; } };

Což se projeví při opakovaném požadavku na tvorbu zlata:

int main() { Flyweight f; cout << f.Get("Gold") << endl; cout << f.Get("Lead") << endl; cout << f.Get("Gold") << endl; cout << f.Get("Iridium") << endl; cout << f.Get("Gold") << endl; return 0; }

Fond

Zatímco odlehčení se používá při tvorbě objektů, fond se uplatňuje při jejich rušení: místo abychom objekt vymazali z paměti, necháme ho uložený pro pozdější využití.

Příklad

Mějme tutéž třídu Heavy jako v předchozím případě:

class Heavy { public: property<string> contents; Heavy() { } Heavy(const Heavy&) { } Heavy(string contents) : contents(contents) { cout << "Creating " << contents; cout << ", consuming lots of resources." << endl; } };

Místo mazání jí nastavíme vlastnost used určující, je-li využívána. K tomu můžeme využít vícenásobnou dědičnost:

struct PoolAble { property<bool> used; PoolAble() : used(true) { } }; struct HeavyPool : public Heavy, public PoolAble { HeavyPool() : Heavy(), PoolAble() { } HeavyPool(const HeavyPool&) : Heavy(), PoolAble() { } HeavyPool(string contents): Heavy(contents), PoolAble() {} };

Ke třídě HeavyPool, která bude metodou setObject() provádět resetování odloženého objektu, který bude odkládat metoda Free(). Metoda Alloc pak recykluje odložený objekt, případně vytvoří nový, jsou-li všechny využívány:

class Pool { typedef vector<HeavyPool*> data; data storage; void setObject(HeavyPool* hp, string key) { hp->used = true; cout << hp->contents << " is now "; hp->contents = key; } public: void Free(int index) { cout << "Freeing " << storage[index]->contents << endl; storage[index]->used = false; } HeavyPool* Alloc(string key) { data::iterator it; for(it=storage.begin(); it<storage.end(); it++) if((*it)->used == false) { setObject(*it, key); return *it; } HeavyPool* hp = new HeavyPool(key); storage.push_back(hp); return hp; } };

Funkčnost příkladu otestuje třeba tento klient:

int main() { Pool p; cout << p.Alloc("Gold")->contents << endl; cout << p.Alloc("Lead")->contents << endl; p.Free(0); cout << p.Alloc("Iridium")->contents << endl; cout << p.Alloc("Platinum")->contents << endl; return 0; }

Pozor na antivzor

Resetování objektu často nebývá efektivnější než jeho znovuvytvoření (což je i tento případ: řetězec se stejně vytváří znovu na jiném místě paměti). V tom případě použití fondu postrádá smysl: zavádí do aplikace implementační složitost, aniž by přinášel jakoukoli výhodu. Tento antivzor se jmenuje Cesspool, znamená doslovně žumpa, ale s ohledem na použití by se mohl překládat jako Hřbitov (není vhodné odtamtud cokoliv vybírat ke znovupoužití).