Programujesz w Unity? Ten kreacyjny wzorzec projektowy musisz znać

Rozpoczęcie przygody w programowaniu nie jest łatwe, a kwestia tego, jak łączyć jedne klasy z drugimi, prędzej czy później uderzy każdego, kto nie odbije się od linijki “Hello World”. Jeśli zajmujecie się programowaniem w Unity, to jestem prawie pewny, że przynajmniej kojarzycie “singletony”, czyli konkretny kreacyjny wzorzec projektowy, którego po prostu trzeba znać.
Programujesz w Unity? Ten kreacyjny wzorzec projektowy musisz znać

Czym jest wzorzec Singleton?

Singleton zalicza się do wzorców kreacyjnych, co oznacza, że jest bezpośrednio związany z tworzeniem obiektów w oprogramowaniu i odpowiada za kontrolowanie i tworzenie obiektów. W przypadku tego wzorca kreacyjnego gwarantuje się, że implementująca go klasa ma tylko jedną instancję, a inne mogą się do niej bezpośrednio odwoływać, jako że jest publiczna globalnie. Takie podejście ma zarówno swoje zalety, jak i wady, ale początkujący pracujący nad małymi projektami mogą być pewni, że wzorzec Singleton spisze się u nich idealnie.

Czytaj też: Jak stworzyć grę w Unity? Stawiam repozytorium, Gita i projekt w Unity

Jak wprowadzić wzorzec Singleton do klasy?

Zastosowanie wzorca Singleton jest proste. W celu wprowadzenia go do klasy, należy w pierwszej kolejności uczynić domyślny konstruktor klasy prywatnym, aby uniemożliwić tworzeniu ich kolejnych instancji oraz stworzyć statyczną zmienną jego instancji. Ostatnim etapem jest stworzenie metody statycznej, która to będzie pełnić funkcję konstruktora.

Przy jej pierwszym wywołaniu instancja zostanie stworzona i zwrócona, a przy następnych będzie zapewniać wyłącznie dostęp do niej. Tak oto uniemożliwia się innym obiektom tworzenie nowych instancji klasy Singleton i uzyskiwanie dostępu do niej poprzez metodę statyczną.

Praktyczna implementacja wzorca Singleton w Unity

Dzięki wprowadzeniu wzorca Singleton do klasy Player w Unity (blok kodu poniżej), zapewniamy sobie prosty i działający całkowicie w tle proces tworzenia instancji w funkcji Awake, która wywołuje się na samym starcie gry. Inne skrypty mogą następnie z łatwością komunikować się z klasą Player, a że mamy pewność, że gracz będzie tylko jeden, a jego śmierć jest równoznaczna z końcem gry i koniecznością przeładowania sceny, możemy zapomnieć o największej wadzie wzorca Singleton.

Czytaj też: Jak powstają gry wideo? Pracuję w gamedevie i opowiadam, jak się to robi

Nie jest to wprawdzie idealna implementacja, bo instancja stworzy się od razu, a nie przy potrzebie jej wykorzystania, ale w przypadku gry nie jest to problemem, bo do instancji tej odwołujemy się od razu, aby utrzymać postać gracza na platformie. Podobna sprawa ma miejsce z instancją AudioSceneManager, która to jest potrzebna natychmiastowo, aby aktywować dźwięki tła.

Wady i zalety wzorca Singleton

W przypadku Unity wzorzec Singleton jest jednym z najprostszym sposobów na łączenie ze sobą klas przy jednoczesnym zapewnieniu przeskoczenia największych bolączek silnika (m.in. odpinaniem się powiązań skryptów w silniku). Jest przy tym idealny do mniejszych projektów, kiedy pewne jest, że będzie potrzebna tylko jedna instancja klasy np. Player, co jednak nie przejdzie w momencie, kiedy będziemy wprowadzać np. tryb kooperacyjny.

Przy stosowaniu wzorca Singleton, musielibyśmy w tej sytuacji powielić klasę Player i zapewnić odwołania do niej innym skryptom, co oznaczałoby np. potrzebę sprawdzania zarówno instancji Player, jak i Player2 przy weryfikacji zebrania money (do której klasy gracza go dopisać). Wzorzec ten rodzi też problemy na etapie optymalizacji po stronie procesora, ponieważ dążenie do rozbicia operacji na wątki wymaga stosowania wielu zabezpieczeń, jako że dwa różne wątki mogłyby stworzyć dwie oddzielne instancje klasy i musieć niepotrzebnie oczekiwać na poszczególne operacje, aby te zwolniły blokadę z tej instancji.

Wzorzec Singleton rodzi też problemy już w samym założeniach, bo przeczy dwóm zasadom SOLID. Mowa zarówno o zasadzie jednej odpowiedzialności (S), a więc tego, że nigdy nie powinno być więcej, niż jednego powodu do istnienia klasy, oraz o zasadzie otwarte-zamknięte (O), czyli tym, że klasa powinna być otwarta na rozszerzenie, ale zamknięta na modyfikację. Singleton łamię tę drugą zasadę, ponieważ po wprowadzeniu go do klasy, wzorzec ten kontroluje jej tworzenie, podczas gdy konsumenci będą zazwyczaj mieli twardą zależność od jej konkretnej instancji. To uniemożliwia zmianę implementacji, bez konieczności wprowadzania zamaszystych zmian w całej aplikacji.

Jednocześnie jego największa zaleta jest też wadą, bo globalny dostęp do unikalnej instancji klasy rodzi problemy po stronie testowania i debugowania. Aktualnie wzorzec Singleton jest przysłowiowym czarnym koniem dla deweloperów, którzy mogą być skorzy do implementowania go “na ślepo” i tam, gdzie nie jest to konieczne. Sam w sobie wzorzec może zapewnić łatwy i szybki proces powstawania oprogramowania (zwłaszcza na etapie prototypowania), ale nieoczekiwanie może też stać się wielkim problemem, którego wystąpienie, wymusza nierzadko całkowite przerobienie wszystkich skryptów.