Singleton
Cel
Singleton jest kreacyjnym wzorcem projektowym, który pozwala zapewnić istnienie wyłącznie jednej instancji danej klasy. Ponadto daje globalny punkt dostępowy do tejże instancji.
Problem
Wzorzec Singleton rozwiązuje jednocześnie dwa problemy, ale jednocześnie łamie Zasadę pojedynczej odpowiedzialności:
-
Zapewnia istnienie wyłącznie jednej instancji danej klasy. Dlaczego w ogóle ktokolwiek musiałby liczyć obiekty danej klasy? Otóż, najczęstszym powodem jest potrzeba kontroli dostępu do jakiegoś współdzielonego zasobu — na przykład bazy danych, lub pliku.
Działa to tak: wyobraź sobie, że masz już stworzony obiekt, ale po jakimś czasie potrzebujesz kolejnego. Zamiast otrzymać nowy, dostaniesz ten uprzednio stworzony.
Zwróć uwagę, że takiego zachowania nie da się zaimplementować stosując zwykły konstruktor, ponieważ metody te z definicji muszą zawsze zwracać nowe obiekty.
-
Pozwala na dostęp do tej instancji w przestrzeni globalnej. Pamiętasz te globalne zmienne, z których korzystaliśmy (no dobra, ja korzystałem) do przechowywania istotnych obiektów? Chociaż są one bardzo poręczne, to wiążą się też z poważnym ryzykiem nadpisania ich zawartości i tym samym awarii programu.
Zupełnie, jak w przypadku zmiennej globalnej, wzorzec Singleton pozwala skorzystać z jakiegoś obiektu w dowolnym miejscu programu. Jednakże, zapewnia też ochronę tego obiektu przed działaniami innego kodu.
Jest też inna strona tego problemu: nie chcemy, aby kod, który pozwala nam rozwiązać pierwszy z powyższych problemów był porozrzucany po całym programie. Dużo lepiej jest trzymać go w jednej klasie, szczególnie, jeśli reszta kodu już od niego zależy.
Wzorzec Singleton stał się obecnie tak popularny, że czasem nazywa się Singletonem rozwiązania odnoszące się tylko do jednego z powyższych problemów.
Rozwiązanie
Wszystkie implementacje wzorca Singleton współdzielą poniższe dwa etapy:
- Ograniczenie dostępu do domyślnego konstruktora przez uczynienie go prywatnym, aby zapobiec stosowaniu operatora
new
w stosunku do klasy Singleton. - Utworzenie statycznej metody kreacyjnej, która będzie pełniła rolę konstruktora. Za kulisami, metoda ta wywoła prywatny konstruktor, aby utworzyć instancję obiektu i umieści go w polu statycznym klasy. Wszystkie kolejne wywołania tej metody zwrócą już istniejący obiekt.
Jeżeli twój kod ma dostęp do klasy Singleton, to będzie mógł wywołać jej statyczną metodę i tym samym za każdym razem otrzyma ten sam obiekt.
Analogia do prawdziwego życia
Rząd jest doskonałym przykładem wzorca Singleton. Kraj może mieć wyłącznie jeden oficjalny rząd. Niezależnie od składu personalnego członków rządu, pojęcie “Rząd kraju X” jest uniwersalnym odwołaniem do organu władzy kraju.
Struktura
-
Klasa Singleton deklaruje statyczną metodę
getInstance
, która zawsze zwraca tę samą instancję swej klasy.Konstruktor klasy Singleton musi być ukryty przed kodem klienckim. Tylko i wyłącznie wywołanie metody
getInstance
powinno dawać dostęp do takiego obiektu.
Pseudokod
W poniższym przykładzie, połączenie z bazą danych zrealizowane jest jako Singleton. Klasa ta nie ma publicznie dostępnego konstruktora, więc aby uzyskać dostęp do jej instancji, trzeba wywołać metodę getInstance
. Metoda ta zachowuje pierwszy stworzony egzemplarz klasy i zwraca go przy każdym kolejnym wywołaniu.
Zastosowanie
Korzystaj z wzorca Singleton, gdy w twoim programie ma prawo istnieć wyłącznie jeden ogólnodostępny obiekt danej klasy. Przykładem może być połączenie z bazą danych, którego używa wiele fragmentów programu.
Wzorzec projektowy Singleton uniemożliwia tworzenie obiektów danej klasy inaczej, niż przez stosowną metodę kreacyjną. Ta z kolei zwróci albo nowy obiekt, albo wcześniej stworzony.
Stosuj wzorzec Singleton gdy potrzebujesz ściślejszej kontroli nad zmiennymi globalnymi.
W przeciwieństwie do zmiennych globalnych, wzorzec Singleton gwarantuje istnienie tylko jednego obiektu danej klasy. Nic, oprócz samej klasy, nie jest w stanie zamienić tego obiektu.
Zwróć uwagę, że zawsze można zmienić ograniczenie dotyczące ilości i pozwolić na jakąś inną maksymalną liczbę jej instancji. Wystarczy zmienić jeden fragment kodu — metodę getInstance
.
Jak zaimplementować
-
Dodaj prywatne, statyczne pole klasy celem przechowywania w nim instancji singleton.
-
Zadeklaruj publicznie dostępną, statyczną metodę kreacyjną która daje dostęp do instancji klasy singleton.
-
Zaimplementuj w tej metodzie statycznej “leniwą inicjalizację”. Oznacza to, że nowy obiekt powinien być tworzony tylko przy pierwszym wywołaniu metody i zdeponowany w statycznym polu klasy. Przy kolejnych wywołaniach, metoda powinna zwracać już istniejący egzemplarz.
-
Uczyń konstruktor klasy prywatnym. Statyczna metoda klasy będzie miała do niego dostęp, ale obiekty innych klas — już nie.
-
Przejrzyj kod kliencki i zamień wszystkie bezpośrednie wywołania konstruktora klasy singleton na wywołania jej statycznej metody kreacyjnej.
Zalety i wady
- Masz pewność, że istnieje tylko jedna instancja klasy.
- Zyskujesz globalny dostęp do tej instancji.
- Obiekt singleton inicjalizowany jest dopiero wtedy, gdy jest po raz pierwszy potrzebny.
- Łamie Zasadę pojedynczej odpowiedzialności. Wzorzec rozwiązuje bowiem dwa różne problemy jednocześnie.
- Zastosowanie wzorca Singleton może zamaskować niewłaściwe projektowanie. Można na przykład doprowadzić do sytuacji, w której komponenty programu wiedzą zbyt wiele o sobie nawzajem.
- Wzorzec ten wymaga specjalnej uwagi w środowisku wielowątkowym, w którym trzeba unikać tworzenia wielu instancji singletona przez wiele wątków.
- Utrudnieniu mogą ulec testy jednostkowe kodu klienckiego klasy singleton, ponieważ wiele frameworków testujących polega na dziedziczeniu przy produkcji atrap obiektów. Ponieważ konstruktor klasy Singleton jest prywatny, a nadpisywanie statycznych metod jest niemożliwe w większości języków programowania, będzie trzeba wykazać się kreatywnością i znaleźć jakiś inny sposób tworzenia atrapy singletona. Albo nie pisać testów, albo zrezygnować z tego wzorca.
Powiązania z innymi wzorcami
-
Klasa Fasada może często być przekształcona w Singleton, ponieważ pojedynczy obiekt fasady jest w większości przypadków wystarczający.
-
Pyłek mógłby przypominać Singleton, gdybyśmy zdołali zredukować wszystkie współdzielone stany obiektów do tylko jednego obiektu-pyłka. Ale są jeszcze dwie fundamentalne różnice między tymi wzorcami:
- Powinna istnieć tylko jedna instancja interfejsu Singleton, zaś instancji Pyłka będzie wiele, o różnym stanie wewnętrznym.
- Obiekt Singleton może być zmienny. Pyłki są zaś niezmienne.
-
Fabryki abstrakcyjne, Budowniczych oraz Prototypy można zaimplementować jako Singletony.