Lekce 2 - Singleton (jedináček)
V minulé lekci, Factory (tovární metoda), jsme si představili návrhový vzor Factory, který odděluje vytváření instance od samotného programu.
V tutoriálu Návrhové vzory GoF si představíme návrhový vzor Singleton, který umožňuje zajistit globální přístup k instanci nějaké třídy.
Motivace
V programu někdy potřebujeme sdílet jednu instanci mezi několika bloky, objekty atd., aniž bychom ji museli stále předávat v konstruktoru. Ukázkový příklad je databázové připojení. Celý program pracuje s jedním připojením a bylo by nepraktické ho stále předávat. Nabízí se udělat v řešení statickou třídu poskytující databázové API. Může však nastat případ, kdy se nám hodí ji mít instanciovatelnou (např. někdy pracujeme s více připojeními) nebo používáme hotovou třídu, která statická není. Vložíme ji tedy do vzoru Singleton.
Definice vzoru Singleton
Vzor je tvořen třídou, která se stará o to, aby její instance existovala jen jednou.
Jako první musíme uživateli zakázat tvořit instanci. Docílíme toho implementací prázdného privátního konstruktoru. Dále vytvoříme běžnou instanční proměnnou a do ní vložíme instanci, kterou chceme v programu sdílet. V našem případě tedy instanci databázového připojení.
Nyní si třída vytvoří instanci sebe sama a tu uloží do statické proměnné. Instanci má takto ve správě třída a uživatel se k ní jinak než přes ni nedostane, protože ji nemůže vytvořit. Máme ji tedy zcela pod kontrolou. Instanci nastavíme samozřejmě jako privátní a také pouze pro čtení.Nakonec vytvoříme veřejnou metodu, přes kterou budeme zvenku k instanci přistupovat. Uvnitř instanci vrátíme.
Singleton znamená jedináček, tedy instance, která nemá žádné sourozence.
Singleton přepsaný do kódu vidíme zde:
-
public sealed class Singleton { private static readonly Singleton instance = new Singleton(); public Databaze databaze = new Databaze("host", "jmeno", "heslo"); private Singleton() {} public static Singleton VratInstanci() { return instance; } }
-
class Singleton { private Singleton() { } public Databaze databaze = new Databaze('host', 'jmeno', 'heslo'); private static Singleton instance = new Singleton(); public static Singleton vratInstanci() { return instance; } }
-
class Singleton { // datový typ vlastnosti až od PHP verze 7.4 private static Singleton $instance; public static Databaze $db; private function __construct() { // PHP nepodporuje výrazy ve vlastnostech třídy, je třeba instanciovat v konstruktoru: self::$db = new Databaze('host', 'jmeno', 'heslo'); } // datový typ metody až od PHP verze 7 public static function vratInstanci() : Singleton { return new Singleton(); } }
-
class Singleton { constructor() { if (!Singleton.instance) { Singleton.instance = this; this.database = new Databaze("host", "jmeno", "heslo"); } } static vratInstanci() { return Singleton.instance; } }
-
class Singleton: _instance = None database = None def __new__(cls): if not cls._instance: cls._instance = super().__new__(cls) cls.database = Databaze("host", "jmeno", "heslo") return cls._instance
Použití v programu
K připojení poté v programu přistupujeme takto:
-
Pripojeni pripojeni = Singleton.VratInstanci();
-
Pripojeni pripojeni = Singleton.vratInstanci();
-
$pripojeni = Singleton::vratInstanci();
-
const pripojeni = Singleton.vratInstanci();
-
pripojeni = Singleton()
Možné modifikace
Metoda k navrácení instance přímo vybízí k tomu, abychom inicializaci instance provedli až ve chvíli, kdy ji potřebujeme. Kód tedy můžeme modifikovat takto:
-
class Singleton { private Singleton() { } public Databaze databaze = new Databaze('host', 'jmeno', 'heslo'); private static Singleton instance = null; public static Singleton VratInstanci() { if (instance == null) instance = new Singleton(); return instance; } }
-
public class Singleton { private static Singleton instance = null; public Databaze databaze = new Databaze("host", "jmeno", "heslo"); private Singleton() {} public static Singleton vratInstanci() { if (instance == null) { instance = new Singleton(); } return instance; } }
-
class Singleton { // datový typ vlastnosti až od PHP verze 7.4 private static Singleton $instance; public static Databaze $db; private function __construct() { // PHP nepodporuje výrazy ve vlastnostech třídy, je třeba instanciovat v konstruktoru: self::$db = new Databaze('host', 'jmeno', 'heslo'); } // datový typ metody až od PHP verze 7 public static function vratInstanci() : Singleton { if (self::$instance === null) { self::$instance = new self; } return self::$instance; } }
-
class Singleton { constructor() { this.databaze = new Databaze('host', 'jmeno', 'heslo'); } static vratInstanci() { if (!Singleton.instance) { Singleton.instance = new Singleton(); } return Singleton.instance; } }
-
class Singleton: _instance = None def __init__(self): self.databaze = Databaze('host', 'jmeno', 'heslo') @classmethod def vratInstanci(cls): if not cls._instance: cls._instance = Singleton() return cls._instance
Někdy takto můžeme optimalizovat výkon programu. Verze není thread safe a v případě, že bychom pracovali s více vlákny, je třeba dát inicializaci instance do locku.
Nevýhody
Slovo globální může být poněkud kontroverzní a Singleton je kvůli tomu někdy označován jako anti-pattern, tedy špatný vzor. Ačkoli Singleton by měl každý programátor znát, určitě to není ideální vzor pro předávání závislostí v aplikaci. Pro své problémy je dokonce součástí antipraktik STUPID. Lepším vyřešením závislostí je vzor Dependency Injection. Pokud vás tato problematika zajímá, doporučujeme náš kurz Softwarové architektury a dependency injection. Thread-safe napsaný Singleton může být výhodné použít ve vícevláknových aplikacích.
V další lekci, Prototype, si ukážeme návrhový vzor Prototype. Prototype vytváří nové instance na základě existujících, tzv. prototypů, které naklonuje.