Wzorzec singleton

Kolejny kreacyjny wzorzec projektowy omawiany na łamach tego bloga: singleton. Wzorzec dookoła którego narosło wiele mitów i legend. Dziś o tym dlaczego singleton jest antywzorcem, jakie problemy powoduje oraz kiedy warto po niego sięgnąć.


Co to singleton?

Singleton jest kreacyjnym wzorcem projektowym, który tworzy i zakłada istnienie tylko jednej instancji tworzonego obiektu na cały system. Stworzony przez singletona byt jest dostępny globalnie, co powoduje wiele problemów związanych z tym wzorcem. Sam singleton ma przypiętą łatkę antywzorca. Dzieje się tak, ponieważ łamie on zasady SOLID (z uwagi m.in. na realizację więcej niż jednego zadania jednocześnie), wprowadza globalny stan, a także jest trudny w testowaniu.

Kiedy używać singleton?

Pomimo wad tego wzorca, istnieje kilka sytuacji, kiedy można zastanowić się nad jego zastosowaniem. Pierwszą z nich jest dobrze każdemu znany przykład połączenia z bazą danych. Zazwyczaj chcemy, aby istniało tylko jedno połączenie z bazą, w tym samym czasie. Do kontroli tego czy istnieje tylko jedna instancja doskonale nada się singleton. Po drugie, kiedy potrzebujemy dokładnej kontroli dostępu do zmiennych globalnych.

Powinniśmy jednak wystrzegać się używania singletonów. Pomijając oczywiste rzeczy (SOLID, stan globalny), testowanie jednostkowe jest bardzo utrudnione, gdyż konstruktor klasy jest prywatny, co praktycznie uniemożliwia automatyczne mock-owanie. Zamiast posiadania globalnych singletonów, lepiej użyć wstrzykiwania zależności i ograniczyć ilość instancji na samym mechanizmie DI (dependency injection).

Jak zaimplementować singleton?

Implementacja tego wzorca sprowadza się do posiadania prywatnego konstruktora klasy, tak, aby możliwość utworzenia nowej instancji dostępna była jedynie z poziomu jej samej. Dodatkowo, należy zdefiniować statyczną metodę, która posłuży do tworzenia obiektu. Metoda ta, w swoim ciele powinna sprawdzać czy klasa została już zainicjalizowana i tworzyć nowy obiekt tylko, jeśli on jeszcze nie istnieje. Na sam koniec, statyczna funkcja kreująca musi zwrócić nowo utworzony, lub już istniejący, zapisany w statycznym polu klasy obiekt.

class DbConnection {
  private static connection?: DbConnection;

  private constructor() {
    // real connection implementation
  }

  static create(): DbConnection {
    if (!DbConnection.connection) {
      DbConnection.connection = new DbConnection();
    }

    return DbConnection.connection;
  } 
}
Oczywiście, samo utworzenie połączenia raczej nic nam nie da. Musimy być w stanie coś z nim zrobić. W obrębie klasy połączenia możemy stworzyć zwykłe metody, np. do uruchamiania zapytań czy zamykania komunikacji. 

class DbConnection {
  private static connection?: DbConnection;

  private constructor() {}

  static create(): DbConnection {
    if (!DbConnection.connection) {
      DbConnection.connection = new DbConnection();
    }

    return DbConnection.connection;
  } 

  run(sql: SQL) {
    // implementation
  } 

  close() {
    DbConnection.connection = null;
  }
}

Podsumowanie




Polecane wpisy:

DRY: Don't Repeat Yourself

Zasada DRY, to po przełożeniu na język polski: nie powtarzaj się. Brzmi bardzo banalnie, jednak dosyć często okazuje się, że mamy problem z jej stosowaniem w kodzie. Dziś dowiesz się, jak nie łamać tej reguły, a co za tym idzie nie powtarzać się.

Sprawdź ten wpis

YAGNI: You aren't gonna need it

YAGNI to kwintesencja zasad clean code. Dotyczy ona bezużyteczności kodu, a dokładniej, konieczności usuwania tych fragmentów, które nie są potrzebne. W myśl "You aren't gonna need it" nie powinniśmy tworzyć niczego więcej, niż to, co jest potrzebne.

Sprawdź ten wpis

KISS: keep it simple, stupid!

Zasada KISS: "Keep it simple, stupid", może zostać dosłownie przetłumaczona na: "rób to prosto, głupku". Mówi ona o tym, abyśmy tworzyli kod w jak najprostszy i najbardziej czytelny sposób. Już dziś sprawdź, czego się wystrzegać, aby spełniać KISS.

Sprawdź ten wpis

Wzorce projektowe

Wzorce projektowe zostały stworzone po to, aby nie wymyślać przysłowiowego koła na nowo. Znajomość wzorców projektowych i umiejętność ich stosowania pozwala na szybkie rozwiązywanie problemów. Wpis ten radzi, jakie wzorce zastosować u siebie.

Sprawdź ten wpis

Wzorzec stan (state)

Dziś prezentuję kolejny wzorzec projektowy, który pozwala uniknąć drabinek if-else if. Mowa tutaj o wzorcu state, który idealnie nada się, jeśli posiadasz różne stany w swoim systemie oraz chcesz mieć możliwość płynnego przechodzenia pomiędzy nimi.

Sprawdź ten wpis

Autor wpisu:

Gabriel Ślawski

Fanatyk czystego i prostego kodu. Zwolennik podejść DDD oraz Modular Monolith. Na codzień pracuje jako programista i architekt. Po godzinach spełnia się w projektach open source, udziela się na blogach oraz czyta książki o kosmosie i astrofizyce.