Wzorce projektowe: Singleton part 2

Ostatnio bardzo długo mnie nie było, głównie przez brak czasu oraz przez to, że tematu do omówienia jest dużo, ale w końcu artykuł jest gotowy, jak się domyślacie z tytułu wpis jest kontynuacją pierwszej części o singletonie bo uznałem, że ta pierwsza część jest zbyt uboga 🙂

 

Wstęp

Cześć wszystkim po długiej przerwie w pisaniu, jest dużo materiału do opisania no i dużo się działo u mnie przez cały ten czas 🙂 Ten artykuł będzie kontynuacją singletona, czyli częścią drugą można powiedzieć, do pierwszej link jest tutaj http://devman.pl/pl/techniki/wzorce-projektowe-1-singletonsingleton/ uznałem, że pierwsza część o singletonie jest zbyt krótka i wiele kwesti związanych z singletonem nie zostało wyjaśnionych, macie przed sobą drogą część o singletonie, która myślę dobitnie wyjaśnia temat 🙂

Użyjemy również dzisiaj testów jednostkowych, żeby zademonstrować jak one współpracują z singletonem, później będzie cały dział na temat testów jednostkowych.

Dużo jest dzisiaj do omówienia, ale damy radę 💪

Jedziemy z koksem 🙂

 

Dyskusja

Trochę też przypomnę do czego jest singleton, najkrócej mówiąc celem singletona jest ograniczenie instancji klasy do jednej i utworzenie do niej globalnego dostępu.

Obiekt klasy singleton tworzy się zwykle(chociaż nie zawsze bo są różne sposoby) poprzez metodę getInstance(), ponieważ konstruktor jest prywatny, więc nie można przesyłać parametrów do konstruktora.

Singletona można użyć wszędzie tam gdzie potrzebujemy mieć tylko jeden obiekt, może on reprezentować np monitor, myszkę, ziemię w jakiejś grze bo jest tylko jedna 🙂 itp.

A jakiś przykład użycia singletona w praktyce to np mechanizm logowania bo wiadomo panel logowania jest tylko jeden, cały serwis np internetowy korzysta z tylko jednego obiektu klasy logowania  :). Przykładem użycia singletona może być np klasa śledząca zdarzenia, bo niepotrzebne są dwie klasy do śledzenia zdarzeń.

 

Cel

  • Ograniczenie instancji klasy do jednego obiektu.
  • Możliwość globalnego dostępu do klasy singletona.

 

Problem

Masz projekt w którym nie możesz duplikować obiektu, musi być tylko jeden, unikatowy obiekt oraz potrzebujesz do tego obiektu globalnego dostępu.

 

Struktura

Schemat wzorca singletona wygląda tak:

Jest on jak widać bardzo prosty, klient wywołuje statyczną metodę getInstance(), która tworzy mu obiekt singletona.

Jak w tabelce powyżej jest opisane, że zdefiniowana zmienna theInstance jest globalna jak również metoda getInstance().

I przechodzimy do roboty 🙂

Sporo jest sposobów zaimplementowania singletona, ale po kolei, zobaczymy najpierw najczęstsze sposoby implementacji singletona. A najczęściej spotykane są dwa sposoby.

 

Eager Initialization

Często spotykany sposób implementacji singletona to tzw eager initialization jego celem jest stworzenie prywatnego konstruktora oraz udostępnienia statycznego pola obiektu a to pole jest zwracane przez metodę GetInstance().

 

Static factory method Singleton

Jest jeszcze sposób implementacji singletona bez metody GetInstance() ze specyfikatorem dostępu public zmiennej INSTANCE.

Jednak sposób Eager initialization jest bardziej praktyczny i elastyczny, ponieważ pozwala na stworzenie fabryki singletonów bez tworzenia typów konkretnych klasy.

To są takie dwa najczęściej spotykane moim zdaniem sposoby implementacji, ale jest ich o wiele więcej, przejdźmy dalej.

 

Lazy Initialization

Kolejny sposób na utworzenie singletona jest tzw leniwa inicjalizacja, czyli utworzenie obiektu klasy singletona wtedy kiedy jest ona potrzebna, dzięki temu nie zajmuje zasobów komputera kiedy nie jest używana.

Wynik:

Jest również w C# sposób, żeby skrócić kod leniwej inicjalizacji w .NET są gotowe funkcje.

Ten kod to jest to samo co pierwszy z punktu Lazy Initialization tylko po prostu krótszy jego zapis 🙂

 

Zabezpieczenie przed wielowątkowością

Problem może być wtedy kiedy będzie trzeba skorzystać z singletona z wielu wątków, bo wtedy wynik z kodu Lazy initialization, może być taki:

Ale i na to jest sposób, żeby zabezpieczyć singletona, bo po co mamy w C# słówko lock 🙂

Teraz wynik zwykle będzie taki 🙂

 

Static Holder singleton

Jest również inny sposób implementacji singletona, singleton zostanie stworzony dopiero po wywołaniu klasy holder.

To rozwiązanie jest dobre kiedy działamy w wielowątkowym środowisku oraz umożliwia nam utworzenie fabryki singletonów.

 

Enum singleton

Jest jeszcze inny sposób na utworzenie singletona z innej klasy 🙂 Jest to sposób bardziej zwięzły, jednak różni się od enuma z javy. Najciekawsze jest to, że tam nie ma w ogóle użytego enuma, jest to taki javowy odpowiednik.

Jak widać zamiast enuma są tu zastosowane typy generyczne, dlatego, że enum działa inaczej w javie a inaczej w C#. W tym przykładzie zrobiliśmy singletona z klasy Product. Minusy są takie, że brak tu lazy loading oraz nie mamy gwarancji, że będzie jeden obiekt klasy Product a to dzięki publicznemu konstruktorowi. Jako, że używamy tutaj typów generycznych to konstruktor musi być publiczny.

 

Trochę tego jest, czas na kawał.

Mówi informatyk do informatyka: – Dość już tego grania, dość programowania, dość już komputera na dziś. Czas coś poczytać. I zaczął czytać e-booka.

 

Prawde mówiąc coś w tym sucharze jest 🙂

 

Stworzenie singletona za pomocą refleksji

Singletona można złamać nawet, jeśli posiadamy prywatny konstruktor, a to dzięki refleksją. Refleksje tworzą obiekt klasy podczas czasu życia program tzw runtime.

Weźmy pod uwagę taki przykład:

W funkcji Main stworzymy singletona za pomocą metody GetInstance() i za pomocą refleksji.

Zobaczmy wynik:

Jak widzimy Hashkody nie są takie same a więc te obiekty singletona nie są takie same. W taki sposób można złamać singletona. Na szczęście jest łatwy sposób, żeby zabezpieczyć singletona przed refleksjami. Wystarczy dodać do konstruktora taki kod:

Teraz dostaniemy taki wyjątek:

 

Zabezpieczenie przed klonowaniem

Wypadałoby również zabezpieczyć singletona przed klonowaniem, bo wiadomo, jeśli byśmy klonowali klasę singletona no to nie byłoby by  tylko jednej tej klasy 🙂 Oto jak to zrobić w .NET:

Wystarczy jedynie dodać i zmienić metodę MemberwiseClone() w przypadku C# jest to dosyć proste 🙂

 

Zabezpieczenie przed serializacją

Można również złamać singletona przez serializację w ten sposób jak pokazałem poniżej:

Najpierw tworzymy obiekt singletona następnie tworzymy ścieżkę do pliku do którego chcemy zapisać ten obiekt. Potem otwieramy ten plik i deserializujemy zapisany obiekt z tego pliku no i złamaliśmy singletona jak widać w wyniku poniżej hashkody nie są takie same.

Wynik:

A jak się zabezpieczyć przed serializacją singletona? W przypadku C# jest to dosyć proste 🙂 Po prostu nie dodajemy słówka Serializable.

A w javie wystarczy dodać metodę readResolve(), żeby uniknąć serializacji, w podsumowaniu dodam jeszcze link do artykułu w którym większość przykładów bardzo podobnych do tych tutaj jest w javie ci co piszą w javie będzie łatwiej przenieść przykłady z tego artykułu do javy.

 

I już pewnie wszyscy czują się jak einstein 🙂

Ale to jeszcze nie koniec, idziemy dalej 🙂

 

Testowanie singletona

Według niektórych singleton jest nietestowalny, jeśli będzie się pamiętać o wyczyszczeniu go przed każdym testem i jeśli ktoś implementuje interfejs singletona, który służy jako jego typ to nie będzie problem ani w testowaniu ani mockowaniu singletona, zobaczymy jak wygląda przykładowe przetestowanie klasy singletona.

Tu są przykładowe testy.

A tu klasa singletona.

Na chwilę obecną dałem jakiś randomowy przykład testów, żeby tylko zobrazować jak to wygląda z singletonem.

Na razie na klasę SomeClass oraz metodę SomeLogic() nie zwracamy uwagi, specjalnie skomplikowałem tą klasę, żeby przedstawić jak wygląda mockowanie, ale to zaraz. Najpierw skupmy się na samych testach.

W naszym przypadku przed każdym wywołaniem testu, tworzymy obiekt singletona, następnie jeśli chcemy wyczyścić klasę singletona to musimy wywołać metodę resetForTesting(), teraz zmienna prywatna _instance będzie pusta.

Teraz zobaczmy jak wygląda mockowanie, to jest klasa do mockowania.

Jak widzimy nie mockujemy klasy singletona tylko jej interfejs przez co mockowanie jest prostsze, ustawiamy atrapę metody SomeLogic() tak, że zawsze będzie zwracała warunek true, nieważne jaki argument przekażemy do metody SomeLogic().

I za pomocą Dependency Injection Przekazujemy atrapę obiektu singletona do klasy SomeClass i wywołujemy metodę SomeLogic() w klasie SomeClass.

Wynik wszystkich testów, oczywiście wszystko się zgadza 🙂

 

Open-closed singleton

Niektórzy mówią, że singleton łamię zasadę open-closed, ale gdy zmienimy typ konkretny singletona na interfejs to już znika ten problem. Przykład poniżej 🙂

 

Praktyczny przykład

Rząd państwowy

Teraz zróbmy jakiś praktyczny przykład z wykorzystaniem singletona, załózmy, że musimy zrobić system, który zlicza liczbę głosów na każdego kandydata na prezydenta i wybiera tego, który ma największą liczbę głosów. Poniżej obrazek do zobrazowania sytuacji.

Zobaczmy jak to wygląda w kodzie

Wszystko jest przeprowadzane w klasie singletona Government. Myśle, że nie muszę tu nic tłumaczyć jest to prosty przykład.

Wynik:

W dodatku połączymy sobie singletona ze wzorcem strategia, załóżmy, że chcemy pozbyć się tych ifów w metodzie Election(), te ify jakoś brzydko wyglądają i na to jest sposób 🙂 spróbujemy chociaż ograniczyć ilość ifów .

Zacznijmy od klas prezydentów.

Klasa singletona oraz interfejs.

Klasa strategii do której przekazujemy stworzone klasy w metodzie Election().

Oraz na końcu klient.

Wynik będzie taki sam jak w przykładzie z wyborami bez strategii.

Oczywiście na pewno jest lepsze rozwiązanie w metodzie Election(), żeby wybierać prezydentów np z wykorzystaniem słownika lub przydzielić prezydentom id, żeby znaleźć prezydenta z największym głosem po id, ale nie chciałem już niepotrzebnie komplikować, chciałem po prostu, żeby było to łatwe do zrozumienia.

Naturalnie w komentarzach zachęcam do zamieszczenia własnego rozwiązania, dla twórcy najlepszego rozwiązania wyśle ciekawe książki w pdf-ie na temat marketingu, sprzedaży lub programowania, będzie można wybrać 🙂

Jak ktoś będzie pisał swoje rozwiązanie w komentarzu proszę podać email w razie żebym wiedział gdzie wysłać te książki 🙂

 

Powiązania z innymi wzorcami

  • Fabryka abstrakcyjna, builder oraz prototyp może użyć singletona w ich instancjach.
  • Klasy Fasady są bardzo często singletonami, ponieważ wymagany jest tylko jeden obiekt Fasady.
  • Obiekty wzorca Stanu są często singletonami.

 

Podsumowanie

No i to koniec drugiej części o singletonie 🙂

Link do githuba ze wszystkimi przykładami:  https://github.com/Slaw145/SingletonPart2

Przez wielu singleton jest uważany za antywzorzec, po części tak jest, ale głównie dlatego, że często jest nadużywany, wystarczy go po prostu używać z rozsądkiem i nie będzie nam w dużym projekcie sypać błędami co pare minut 🙂 .

Wszystkie przykłady są tu w C# jest również fajny artykuł, który pokazuje niektóre te przykłady w javie, dla tych co piszą tylko w javie, myślę, że będzie to duże ułatwienie, tu daje linka: https://www.journaldev.com/1377/java-singleton-design-pattern-best-practices-examples

Chyba przestanę na razie pisać bloga jestem zmęczony 😂😂

Nie spokojnie, żartuje😂 następny artykuł myślę będzie dokładniej o dependency Injection, ale jeszcze zobaczymy.

Standardowo, przypominam o newsletterze, którym wysyłam powiadomienia o nowych wpisach oraz dodatkowe informacje na temat, ogółem mówiąc, świecie IT.

KONIECZNIE dołącz do społeczności DevmanCommunity na fb, części społeczności jest w jednym miejscu 🙂

-strona na fb: Devman.pl-Sławomir Kowalski

-grupa na fb: DevmanCommunity

Pytaj, komentuj pod spodem na końcu wpisu, podziel się nim, oceń go, rób co wolisz🙂

Ilustracje, obrazki oraz diagramy są z: https://sourcemaking.com/design_patterns/singleton

 
Jeśli ten wpis ci się przydał podziel się nim ze swoimi znajomymi :)

Post a comment

avatar
  Subscribe  
Notify about