V tomto článku si představíme jeden z nejznámějších vzorů, zvaný „jedináček“ (Singleton). Jedináček je díky své jednoduchosti a svým vlastnostem oblíben i u řady vývojářů, kteří jinak návrhové vzory příliš v lásce nemají. Jedináček byl společně s abstraktní továrnou použit již v předchozím článku – tentokrát si použití tohoto vzoru rozebereme podrobně a přidáme další příklad možného použití v .NET aplikacích.

Řešená zadání

Řešená zadání budou tedy pro tentokrát dvě, abychom ukázali odlišné možnosti implementace vzoru. Prvním bude podrobnější popis vytváření instance továrny z předchozího článku, druhým bude zajištění přístupu k instanci hlavního formuláře WinForm aplikace z ostatních formulářů bez nutnosti předávání odkazu na tuto instanci.

Pro přiblížení řešení předchozího příkladu – vzor abstraktní továrna byl použit pro vytváření komponent logiky přístupu k datům pro různé typy databázových serverů. Vzor však nijak neřeší vytvoření instance konkrétní továrny a je vhodné, aby proces vytvoření instance byl před klientem skryt. Dále je požadováno, aby případná implementace logiky přístupu pro nový typ serveru byla možná bez zásahu do kódu zbytku aplikace.

Návrh řešení

Obě zadání mají některé body společné. Obě vyžadují snadné zpřístupnění instance určité třídy klientům, u obou se vyžaduje (u hlavního formuláře to vychází přímo z architektury WinForm aplikace), aby tato instance byla v rámci aplikace jen jedna, a u obou je navíc žádoucí, aby vlastní proces tvorby instance byl před klientem skryt. Zadání se liší zejména v požadavku na zaměnitelnost této instance – v případě hlavního formuláře aplikace bude vytvářena vždy instance jediné konkrétní třídy, v druhém případě je požadována snadná zaměnitelnost této třídy pokud možno pouhým zásahem do konfigurace aplikace.

Základní popis vzoru

Singleton se v katalogu GoF vzorů řadí k takzvaným tvořivým objektovým vzorům, z čehož vyplývá, že je určen pro tvorbu objektů a případná změna chování je dosahována výměnou instance objektu.

Vzor lze aplikovat všude tam, kde je požadován (nebo je vhodný) výskyt jediné instance třídy v rámci aplikace a kde tato instance má být klientům snadno dostupná pomocí známého „přístupového bodu“. Oproti zpřístupnění objektu pomocí veřejné statické proměnné třídy přidává singleton možnost snadné rozšiřitelnosti pomocí dědičnosti, a to navíc bez nutnosti změny v kódu svých klientů.

Vzor se skládá z jediné součásti – třídy jedináčka. Třída implementující vzor zapouzdřuje přístup ke sdílené instanci a je také zodpovědná za vytváření této instance. Klient je od procesu vytváření instance odstíněn, pouze k ní přistupuje pomocí již zmíněného „přístupového bodu“.

Struktura a účastníci vzoru

Vzor má velmi jednoduchou strukturu, zachycenou pro řešení sdílení hlavního formuláře na následujícím obrázku:

Struktura vzoru
Struktura vzoru (plná velikost, cca 9 kB)

Z obrázku je patrné, že účastníky vzoru jsou vlastní jedináček (třída MainForm) a klient přistupující k instanci (třída SecondForm).

Další informace o vzoru jsou k dispozici například na Design Patterns: Singleton.

Implementace v C#

Při implementaci vzoru v C# se využívá privátní statická proměnná jako odkaz na instanci, přístupový bod lze realizovat pomocí statické vlastnosti, případně statické metody.

Vlastní vytvoření instance lze realizovat dvěma způsoby. První možností je statická instanciace proměnné při deklaraci, druhá možnost, která by se dala nazvat „zpožděným vytvořením“, vytváří instanci až v okamžiku prvního přístupu klienta k přístupovému bodu.

U statického vytvoření instance je výhodou automatické zajištění bezpečného přístupu z více vláken (takže vývojář se nemusí explicitně starat o synchronizaci vláken při vytváření instance), u druhého způsobu je výhodou vytvoření instance až tehdy, když je opravdu potřeba, a také lepší kontrola nad způsobem vytvoření instance.

V řešení našich zadání použijeme oba způsoby – pro vytvoření instance konkrétní továrny je použito „zpožděné vytvoření“, v případě hlavního formuláře WinForm aplikace je použito vytvoření instance při deklaraci statické proměnné.

Vytvoření instance abstraktní továrny

public abstract class DatabaseFactory
{
  protected DatabaseFactory()
  {}
  private static volatile DatabaseFactory _instance;
  private static object _lock = new object();
  public static DatabaseFactory GetFactory()
  {
    if (_instance == null)
    {
        lock (_lock)
        {
            if (_instance == null)
            {
                Configuration configuration = GetConfiguration();
                try
                {
                    _instance = Activator.CreateInstance(Type.GetType(configuration.Type), true) as DatabaseFactory;
                }
                catch (Exception exc)
                {
                    throw new ApplicationException(„Chyba při vytváření instance továrny.“, exc);
                }
            }
        }
    }
    return _instance;
  }
}

Přístupový bod implementuje metoda GetFactory, odkaz na sdílenou instanci je v proměnné _instance. Protože se budeme sami starat o synchronizaci vláken při vytváření instance (z pohledu vícevláknového přístupu je vytvoření instance kritickou sekcí) a nechceme, aby nám překladač s touto proměnnou prováděl optimalizace, které by synchronizaci mohly ohrozit, označíme proměnnou klíčovým slůvkem volatile.

Pro synchronizaci přístupu do kritické sekce použijeme techniku nazývanou double-checked locking, jako objekt, nad kterým držíme zámek, poslouží proměnná _lock.

Princip této techniky je názorně vidět z výpisu kódu – zkontrolujeme, zda instance, kterou vytváříme, již existuje, a pokud ne, provede se zamčení pomocného objektu _lock a po vstupu do kritické sekce je ještě jednou otestováno, zda instance existuje. Pokud v druhém testu instance neexistuje, vytvoříme ji. Použitá synchronizační technika nám zajistí, že vytvoření instance proběhne i ve vícevláknové aplikaci maximálně jednou.

Více o použití této techniky ve spojitosti s jedináčkem najdete například v článku Implementing Singleton in C#.

Protože chceme, aby se konkrétní DAL továrna dala měnit v konfiguraci aplikace, načítáme z konfiguračního souboru název typu této instance a pomocí reflexe (konkrétně třídy Activator) vytvoříme instanci tohoto typu. Případná implementace DAL komponent pro nový typ DB spočívá v implementaci těchto komponent, implementaci příslušné továrny a uvedení názvu jejího typu do konfiguračního souboru. Zbytek aplikace se díky použití jedináčka o této výměně továrny a implementace DAL komponent vůbec nedozví.

Kompletní příklad řešení je vám k dispozici.

Sdílení hlavního formuláře ve WinForm aplikaci

Implementace hlavního formuláře WinForm aplikace jako jedináčka je díky statické instanciaci ještě jednodušší. Vytvoření instance je provedeno následujícím řádkem:

private static MainForm _instance = new MainForm();

Privátní konstruktor zajistí, že instanci třídy nebude možné získat jinak, než pomocí nadefinovaného přístupového bodu, zde ve formě vlastnosti nazvané Instance:

private MainForm()
{
  InitializeComponent();
}
public static MainForm Instance
{
  get
  {
    return _instance;
  }
}

Třída má pro ukázku funkčnosti implementovánu metodu ShowStatusText, která zobrazí text předaný jako parametr na stavové liště hlavního formuláře:

public void ShowStatusText(string text)
  _statusBar.Text = text;
}

Pro úplnost musíme ještě přepsat statickou vstupní metodu Main tak, aby jako parametr metody Application.Run předávala naši sdílenou instanci:

[STAThread]
static void Main()
{
  Application.Run(MainForm.Instance);
}

Pokud chceme z dalšího formuláře v aplikaci zobrazit v hlavním okně nějaký text ve stavové liště, provedeme to následujícím voláním:

MainForm.Instance.ShowStatusText(_statusText.Text);

V příkladu je toto demonstrováno z formuláře „SecondForm“, který takto zobrazuje text zadaný uživatelem po stisku tlačítka Show.

Kompletní příklad řešení je vám opět k dispozici.

Doporučená literatura

Starší komentáře ke článku

Pokud máte zájem o starší komentáře k tomuto článku, naleznete je zde.

Žádný příspěvek v diskuzi

Odpovědět