IT rekvalifikace s garancí práce. Seniorní programátoři vydělávají až 160 000 Kč/měsíc a rekvalifikace je prvním krokem. Zjisti, jak na to!
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 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.


 

Předchozí článek
Factory (tovární metoda)
Všechny články v sekci
Návrhové vzory GoF
Přeskočit článek
(nedoporučujeme)
Prototype
Článek pro vás napsal David Hartinger
Avatar
Uživatelské hodnocení:
139 hlasů
David je zakladatelem ITnetwork a programování se profesionálně věnuje 15 let. Má rád Nirvanu, nemovitosti a svobodu podnikání.
Unicorn university David se informační technologie naučil na Unicorn University - prestižní soukromé vysoké škole IT a ekonomie.
Aktivity