Vydělávej až 160.000 Kč měsíčně! Akreditované rekvalifikační kurzy s garancí práce od 0 Kč. Více informací.
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

Lekce 3 - Object pool (fond objektů)

V minulé lekci, Servant (Služebník), jsme si ukázali návrhový vzor Služebník. Už víme, že ho použijeme v situaci, kdy chceme skupině tříd přidat určitou funkčnost, aniž bychom ji do nich přímo zabudovali.

Návrhový vzor Object pool nebo Object fond (v anglické literatuře se objevuje pojem „pool“, v článku budu používat pojem „fond“) nám uchovává objekty na pozdější použití. To se nám hodí v situacích, kde je vytváření instance příliš složité nebo trvá příliš dlouho.

Motivace

Pracujeme-li na mnoha místech se třídou, která je složitá na vytváření, její opakovaná konstrukce nám zpomaluje celý program. Návrhovým vzor object fond se sám stará o vytváření a předávání objektů zbytku programu s tím, že již vytvořené objekty nemaže, ale uchovává na další použití.

Příklad

Přejdu hned k praktické části. Snad každá webová aplikace využívá databázi pro uchovávání dat. S tím souvisí připojení k samotné databázi, kdy si aplikace musí vytvořit spojení. Takové spojení může v extrémních případech trvat i několik sekund. Pokud takových připojení vytváříme v aplikaci více (například kvůli více vláknům, nebo dokonce pro každý dotaz), je odezva aplikace vysoká.

Z této situace nás zachrání fond. V našem případě bude udržovat připojení k databázi. Pokud se bude nějaká část aplikace chtít dotázat na něco databáze, požádá fond o připojení. Jakmile dotaz provede, vrátí připojení zpět fondu, který ho „zrecykluje“ a může poté předat další části programu, která o připojení požádá. Hlavní přínos tedy spočívá v eliminaci opětovného vytváření připojení, které na celé operaci trvá nejdéle.

Implementace

Nejprve se podíváme, jak by takový fond z minulého příkladu vypadal, a poté si vysvětlíme jeho funkčnost:

class FondPripojeni
{
    private string JmenoServeru = "MujServer";
    private string Databaze = "Northwind";
    private List<SqlConnection> Connections = new List<SqlConnection>();

    public SqlConnection ZiskejPripojeni()
    {
        if (Connections.Count == 0)
            return new SqlConnection($"Server={$JmenoServer}; Database={$Database}; Trusted_Connection = true");
        else
        {
            SqlConnection Vratit = Connections[0];
            Connections.RemoveAt(0);
            return Vratit;
        }
    }

    public void VratitPripojeni(SqlConnection Pripojeni)
    {
        Connections.Add(Pripojeni);
    }
}

Fond má v základní implementaci pouze dvě metody. Jedna vrací připojení, pomocí druhé metody připojení vracíme zpět fondu. Teprve když vrátíme připojení zpět fondu, může jej fond předat jiné části programu. Abychom si situaci demonstrovali, použiji následující program. Vytvoří se 20 vláken, které se uspí na náhodnou dobu. Po probuzení požádají fond o připojení. Aby bylo lépe vidět, že se vytváří méně připojení, než je vláken, budu u každého připojení vypisovat informaci o tom, že bylo právě vytvořeno:

class Program
{
    static Random Ran = new Random();
    static FondPripojeni Fond = new FondPripojeni();

    static void Funkce()
    {
        int DelkaUspani = Ran.Next(0,3000);
        Thread.Sleep(DelkaUspani);
        Console.WriteLine("Žádost o objekt");
        SqlConnection Pripojeni=Fond.ZiskejPripojeni();
        // SIMULACE PRÁCE S OBJEKTEM
        Thread.Sleep(Ran.Next(0,200));
        Fond.VratitPripojeni(Pripojeni);
    }

    static void Main(string[] args)
    {
        for (int a = 0; a < 20; a++)
        {
            Thread vlakno = new Thread(Funkce);
            vlakno.Start();
        }
        Console.ReadKey();
    }
}

Výstup programu:

Žádost o objekt
Připojení bylo vytvořeno
Žádost o objekt
Žádost o objekt
Žádost o objekt
Žádost o objekt
Žádost o objekt
Připojení bylo vytvořeno
Žádost o objekt
Žádost o objekt
Žádost o objekt
Žádost o objekt
Žádost o objekt
Žádost o objekt
Žádost o objekt
Žádost o objekt
Žádost o objekt
Žádost o objekt
Žádost o objekt
Žádost o objekt
Připojení bylo vytvořeno
Žádost o objekt
Žádost o objekt

Jak je vidět, ačkoliv chtělo 20 vláken připojení, ve skutečnosti se vytvořily připojení pouze 3. Kdyby si každé vlákno vytvořilo vlastní připojení, zvýší se potřebný čas, který bude potřeba pro výpočet, dále bude více zahlcena linka i samotná databáze, protože bude muset udržovat několik spojení současně.

Možná konfigurace

Některé objekty nám mohou zabírat mnoho místa. Použijeme-li je ve fondu, může nám rychle dojít paměť. V takových situacích zpravidla nechceme mít neomezený počet existujících instancí. Můžeme dát fondu parametr, který nám řekne maximální počet instancí, které má uchovávat. Pokud by chtělo vlákno další instanci, může metoda ZiskejPripojeni() například vyhodit výjimku, nebo vrátit null. Druhý přístup spočívá v tom, že si nadefinujeme dvě metody: ZiskejPripojeniIhned() a ZiskejPripojeniCekam(). V prvním případě by se (nejsou-li žádné volné objekty) vytvořila nová instance. Při navrácení objektů zpět by se objekt, který by byl již nad hranici, neuložil zpět, ale rovnou smazal. Při volání funkce ZiskejPripojeniCekam() by se vlákno zablokovalo do doby, než bude volná instance ve fondu. Který způsob použít závisí na konkrétní situaci. V našem případě optimalizujeme především mezi časem a pamětí.

Hlavní problém, který s Fondem souvisí, je, že musíme vracet objekty zpět. Na první pohled se jedná o relativně jednoduchý princip, ale ne všichni programátoři ho dodržují. Prvním problém, který může nastat, je, že programátor bude objekt z fondu používat i po jeho navrácení zpět. Tato situace lze vyřešit obalením návratového objektu – viz. článek o proxy. Proxy zajistí, aby objekt nešel použít po tom, co se navrátí zpět do fondu. Druhým případem je situace, kdy programátor objekt vůbec vracet nebude. S takovou situací se vypořádáme jen velmi těžko a zde záleží na konkrétním jazyce. Opět by se uplatnil návrhový vzor proxy, ale například v C++ by se vracela hodnota přímo (ne ukazatelem). Tím by bylo zajištěno, že se objekt vrátí zpět do Fondu po opuštění kontextu (například v destruktoru proxy). V C# bychom mohli například definovat proxy odvozené od IDisposable, který sice pořád vyžaduje nějakou akci od programátora, ale stále je to více než nic.

Vícevláknové aplikace

Ačkoliv byla první ukázka demonstrována pomocí více vláken, v reálné aplikaci se o vhodnou implementaci nejedná. Fond se vůbec nestará o synchronizaci, jak by tomu mělo být. Níže je ukázka fondu tak, jak by měl být implementován. Vícevláknové mechanismy jsou mimo obsah tohoto článku, proto pro plné pochopení doporučuji přečíst seriál Paralelní programování a vícevláknové aplikace v C#.NET. Protože budu používat více tříd, a nechci zahltit celý článek kódem, kompletní řešení bude v přiloženém archivu pod článkem. Spojíme zde synchronizaci, maximální počet instancí i navrácení zpět do Fondu bez přímého volání metody. Zde uvedu jen UML diagram, jaký je výsledek:

Diagram - Ostatní návrhové vzory

Všimněme si, jak se nám kód rozrostl. To je daň za to, kdy chceme pracovat s více vlákny současně. Také bych chtěl upozornit na metodu ZiskejPripojeniPockam(). Vlákno pravidelně uspáváme do doby, než se uvolní objekt, který potřebujeme. V rámci jednoduchosti je metoda definována tímto způsobem, ale lze dále optimalizovat (třeba pomocí semaforů), to je ovšem mimo zaměření tohoto článku.

Závěr

Fond použijeme v programech, kde chceme zamezit opětovnému vytváření instance, nebo chceme mít kontrolu nad tím, kolik objektů existuje. Můžeme počet existujících objektů kontrolovat, a to i když při psaní kódu nevíme, kolik objektů bude aplikace reálně potřebovat.

V další lekci, Immutable objects (neměnné objekty), si probereme neměnné objekty, tzv. immutable objects. Výhody a využití tohoto návrhového vzoru si představíme.


 

Stáhnout

Stažením následujícího souboru souhlasíš s licenčními podmínkami

Staženo 755x (4.54 kB)

 

Předchozí článek
Servant (Služebník)
Všechny články v sekci
Ostatní návrhové vzory
Přeskočit článek
(nedoporučujeme)
Immutable objects (neměnné objekty)
Článek pro vás napsal Patrik Valkovič
Avatar
Uživatelské hodnocení:
12 hlasů
Věnuji se programování v C++ a C#. Kromě toho také programuji v PHP (Nette) a JavaScriptu (NodeJS).
Aktivity