Singleton to kreacyjny wzorzec projektowy gwarantujący istnienie tylko jednego obiektu danego rodzaju. Udostępnia też pojedynczy punkt dostępowy do takiego obiektu z dowolnego miejsca w programie.
Singleton charakteryzuje się prawie takimi samymi zaletami i wadami jak zmienne globalne i chociaż jest bardzo poręczny, to jednak psuje modularność kodu.
Nie można przenieść klasy zależnej od Singletona i użyć jej w innym kontekście bez równoczesnego przeniesienia tego drugiego. To ograniczenie zazwyczaj ujawnia się na etapie tworzenia testów jednostkowych.
Na ogół instancja singletona tworzona jest przy okazji pierwszej inicjalizacji struktury. Aby tak się stało, definiujemy metodę getInstance struktury. Metoda ta będzie odpowiedzialna za tworzenie i zwracanie instancji singletona. Za każdym razem gdy wywoła się metodę getInstance, zwraca będzie ta sama instancja singletona.
A co z goroutines? Struktura singleton musi zwracać tę samą instancję za każdym razem gdy wiele goroutines chce uzyskać dostęp do tej instancji. Na skutek tego łatwo można zaimplementować wzorzec singleton niewłaściwie. Poniższy przykład przedstawia prawidłowy sposób tworzenia singletona.
Na co warto zwrócić uwagę:
Na starcie upewniamy się, że singleInstance jest wstępnie pusty. Dzięki temu zapobiegamy kosztownym operacjom zakładania blokady przy każdym wywołaniu metody getInstance. Jeśli wynik sprawdzenia jest różny niż null, oznacza to, że pole singleInstance jest już zapełnione.
W obrębie blokady tworzona jest struktura singleInstance.
Kolejne sprawdzenie null wykonuje się po założeniu blokady. Dzięki temu mamy pewność, że jeżeli więcej niż jedna goroutine obejdzie pierwsze sprawdzenie to tylko jedna z nich będzie mogła utworzyć instancję singletona. W przeciwnym wypadku wszystkie goroutines utworzą swoje własne instancje struktury singleton.
single.go: Singleton
main.go: Kod klienta
output.txt: Wynik działania
Inny przykład
Są też inne sposoby na tworzenie instancji singletona w Go:
Funkcja init
Możemy stworzyć pojedynczą instancję w obrębie funkcji init. Warunkiem jest pomyślna wstępna inicjalizacja instancji. Funkcja init jest wywoływana raz dla każdego pliku w paczce, możemy mieć więc pewność, że powstanie tylko jedna instancja.
sync.Once
Sync.Once wykona operację tylko jednorazowo. Spójrzmy na poniższy kod: