Główne logo strony
📅

Wzorzec: Singleton design pattern

Singleton design pattern jest jednym z mniej popularnych i mniej stosowanych wzorców projektów w świecie JavaScript. Nie mniej jednak znajdą się przypadki gdy singleton okaże się dobrym rozwiązaniem.

Singleton

Singleton jest wzorcem, który pozwala na stworzenie tylko jednej instancji obiektu z klasy bądź konstruktora funkcyjnego. W przypadku wielokrotnego wywoływania tej samej klasy zawsze będziemy otrzymywali tą samą instancję, która została stworzona podczas pierwszego wywołania. To tyle jeżeli chodzi o samą definicję singletonu. Jak widać nie jest ona zbyt skomplikowana.

Singleton przez niektórych klasyfikowany jest jako anty-wzorzec, ponieważ łamie zasady programowania obiektowego i bywa nadużywany jako alternatywny sposób na tworzenie zmiennych globalnych.

Jako przypadki użycia singletonu możemy tutaj wymienić np.:

  • Obiekty konfiguracyjne. Nie chcemy może za każdym razem generować nowego obiektu z danymi konfiguracyjnymi. Zamiast tego możemy zwracać już raz wygenerowane dane.
  • Połączenie z bazą danych. Zazwyczaj chcemy ustanowić jedno połączenie z bazą a nie inicjować wielu połączeń przy każdym zapisie/odczycie.
  • Logger danych. Duplikowanie logów z aplikacji może wprowadzić sporo problemów przy debugowaniu potencjalnych problemów.

Implementacja

Przedstawimy sobie dwa przykładowe sposoby na to w jaki sposób można stworzyć singleton w JavaScript. We wpisie dotyczącym module design pattern omawialiśmy sobie tworzenie modułów przy użyciu funkcji IIFE, więc również tutaj jedna implementacja będzie oparta na IIFE:

/* --- Singleton --- */
const AppConfig = (function () {
  let config; // prywatna wartość, która zostanie zainicjowana tylko raz
 
  function initializeConfiguration(initData) {
    // funkcja konstruktora
    this.randomNumber = Math.random();
    initData = initData || {};
    this.number = initData.number || 5;
  }
 
  // publiczna część singletonu - API modułu
  return {
    getConfig: function (values) {
      // inicjujemy singleton tylko jeden raz
      if (config === undefined) {
        config = new initializeConfiguration(values);
      }
      // przy kolejnych wywołaniach zawsze zwracamy już te same dane
      return config;
    },
  };
})();
 
const configObject = AppConfig.getConfig({ number: 8 });
console.log(configObject); // patrz zdjęcie poniżej
 
const configObject2 = AppConfig.getConfig({ number: 1 });
console.log(configObject2); // patrz zdjęcie poniżej
 
console.log(configObject.config); // undefined
console.log(config); // ERROR

Oto co zobaczymy w konsoli:

Jak widać na powyższym screenie zarówno losowo generowana wartość dla randomNumber jak i determinowany przez konstruktor number w obydwu przypadkach mają dokładnie takie same wartości. Sama wartość zmiennej config nie może również zostać zmieniona poza modułem stworzonym przez IIFE.

Użycie klasy

W momencie gdy w JavaScript pojawiły się zdefiniowane w standardzie ES6 klasy możemy stworzyć singleton nieco łatwiej i czytelniej. Poniżej znajduje się jeden z najprostszych sposobów.

class AppConfig {
  constructor(number = 5) {
    if (AppConfig.exists) {
      return AppConfig.instance;
    }
    this.randomNumber = Math.random();
    this.number = number;
    AppConfig.exists = true;
    AppConfig.instance = this;
    return this;
  }
}
 
const configObject = new AppConfig(8);
const configObject2 = new AppConfig(1);
 
console.log(configObject);
console.log(configObject2);
console.log(configObject === configObject2); // true

Jak widać w tym przypadku również obiekty, które zostały zwrócone z klas są identyczne.

Singleton design pattern można zaimplementować na kilka różnych sposobów. Powyższe dwa przykłady pokazują jedne z najłatwiejszych sposobów na osiągniecie tego celu. Mimo, iż singleton w aplikacjach JavaScript nie jest wykorzystywany zbyt często – zdecydowanie warto wiedzieć o jego istnieniu.

Masz uwagi lub sugestie do tego wpisu?

discord iconPrzejdź na Discord