Singleton to jeden z podstawowych wzorców projektowych. Warto go pamiętać, gdyż jest on niezwykle przydatny, kiedy mamy do czynienia z implementacjami usług, które wykorzystujemy w całym programie tylko raz, niezależnie od miejsca utworzenia. Najprostszymi słowami można to nazwać połączeniem 1:1 (np. jedna baza danych – jedno połączenie nawiązujące).Rozwiązanie takie jest szczególnie wykorzystywane przy:
- połączeniach z bazą danych.
- połączeniach z urządzeniami wykorzystujących komunikację portów szeregowych.
- bibliotekach działających przez cały okres działania programu np. log4net.
- itp.
Często singleton jest określony jako antywzorzec z tego względu, że zaburza zasady programowania obiektowego, głównie dlatego, że wykorzystuje pola i właściwości statyczne. Jest również kulą u nogi, kiedy próbujemy pokryć testem jednostkowym aplikację wykorzystującą Singleton.
Czym charakteryzuje się singleton? Pełni on następujące funkcje:
- Modyfikatora sealed – uniemożliwi dziedziczenia z tej klasy
- Prywatnego konstruktora, który umożliwia tworzenia instancji klasy
- Prywatnego pola klasy, który jest uchwytem do przechowywania instancji klasy.
- Publicznej statycznej właściwości, która przy pierwszym wywołaniu tworzy instancje klasy, a przy każdym następnym – zwraca wcześniej utworzoną instancje.
Poniżej najprostsza implementacja:
sealed class Singleton // po tej klasie nie ma możliwości dziedziczenia { private Singleton() { } // uniemożliwia tworzenie instancji z klas zewnętrznych private static Singleton instance = null; //statyczne pole przechowujące instancje własnej klasy public static Singleton Instance // właściwość zwracająca jedną instancję klasy { get { if (instance == null) // kiedy instancja nie istnieje... { instance = new Singleton(); // utwórz ja, wykorzystując prywatny konstruktor } return instance; // jeśli instancja istnieje - zwróć ją } } }Często zdarza się tak, że programując, wykorzystujemy wątki. Jeśli dwa wątki jednocześnie zaczną działać na jednej instancji Singletonu, to mogą pojawić się problemy.
Gdy jeden wątek będzie modyfikował wartość pola, np. typu int, a drugi będzie chciał go pobrać, to wątek 2 wykorzystywać będzie tak zwane brudne dane -> inne niż te, które pierwotnie były w klasie i których się spodziewał.
Powyżej opisany przypadek jest i tak jedną z najmniej sympatycznych wizji, jakie mogą się stać w aplikacjach wielowątkowych. W większości przypadków, kiedy więcej niż jeden wątek wchodzi do Singletonu, pojawia się problem współdzielenia obiektów referencyjnych. Takie działanie spowoduje rzucenie wyjątku i zakończy się natychmiastowym końcem działania programu. Aby uniknąć takiej sytuacji stawia się kłódeczkę tuż przed pobraniem instancji.sealed class Singleton // po tej klasie nie ma możliwości dziedziczenia { private static readonly object padLock = new object(); // obiekt "kłódeczki" private Singleton() { } // uniemożliwia tworzenie instancji z klas zewnętrznych private static Singleton instance = null; //statyczne pole przechowujące instancję własnej klasy public static Singleton Instance // właściwość zwracająca jedną instancję klasy { get { lock (padLock) // instrukcja blokująca dostęp więcej niż jednemu wątkowi { if (instance == null) // kiedy instancja nie istnieje... { instance = new Singleton(); // utwórz ją wykorzystując prywatny konstruktor } return instance; // jeśli instancja istnieje - zwróć ją } } } }Modyfikacja polegała na dodaniu obiektu, który będzie zapewniał nam blokadę oraz dodanie instrukcji blokującej (lock) tuż przed próbą pobrania instancji Singletonu. Jeśli w naszym Singletonie nie będzie wykonywał się żaden wątek, to instancja zostanie zwrócona. W przypadku, kiedy jakiś wątek już działa wewnątrz tej klasy, to drugi wątek nie będzie miał możliwości skorzystania z zasobów tej klasy do czasu zwolnienia blokady.