Cześć! Dzisiaj chciałbym przybliżyć Ci jeden z najbardziej rozpoznawalnych (i jednocześnie kontrowersyjnych) wzorców projektowych – Singleton. Wzorzec ten zapewnia istnienie tylko jednej instancji danej klasy w całej aplikacji. Chociaż spotkasz wiele opinii, że Singleton bywa nadużywany, to w pewnych sytuacjach jest wręcz nieoceniony.
Czym jest Singleton?
Singleton to wzorzec projektowy, który zapewnia, że dana klasa ma tylko jedną instancję w skali całej aplikacji i udostępnia globalny punkt dostępu do tej instancji. W praktyce oznacza to, że zamiast tworzyć wiele obiektów tej klasy w różnych miejscach, zawsze odwołujemy się do jednej, wspólnej instancji.
Kluczowe cechy Singletona:
1. Prywatny konstruktor – uniemożliwia tworzenie obiektów z zewnątrz.
2. Statyczne pole przechowujące jedyną instancję.
3. Metoda (lub właściwość) statyczna zwracająca tę jedyną instancję.
Przykład: klasyczny Singleton w C#
Poniżej znajdziesz najprostszy przykład, który często spotkasz w materiałach dotyczących tego wzorca.
public sealed class ClassicSingleton
{
private static ClassicSingleton _instance = null;
// Obiekt 'locker' do obsługi wątków
private static readonly object _lock = new object();
// Prywatny konstruktor
private ClassicSingleton()
{
// Ewentualna logika inicjująca
}
public static ClassicSingleton Instance
{
get
{
if (_instance == null)
{
lock (_lock)
{
if (_instance == null)
{
_instance = new ClassicSingleton();
}
}
}
return _instance;
}
}
// Przykładowa metoda singletona
public void DoSomething()
{
Console.WriteLine("Singleton is working!");
}
}
Omówienie
• sealed – zapobiega dziedziczeniu klasy (wzorzec Singleton nie przewiduje rozszerzania tej klasy).
• Blok lock – zabezpiecza tworzenie instancji w sytuacjach wielowątkowych (np. w aplikacji serwerowej).
• Prywatny konstruktor – uniemożliwia tworzenie obiektów poza klasą.
Uwaga: Powyższy kod stosuje lazy instantiation – obiekt tworzony jest dopiero wtedy, gdy jest potrzebny, a nie od razu podczas uruchamiania aplikacji.
Kod użycia Singletona
Jak skorzystać z tej klasy w praktyce? To bardzo proste. Zobacz:
class Program
{
static void Main(string[] args)
{
// Pobieramy referencję do jedynej instancji Singletona:
var singleton1 = ClassicSingleton.Instance;
var singleton2 = ClassicSingleton.Instance;
// Obie zmienne wskazują na ten sam obiekt
if (ReferenceEquals(singleton1, singleton2))
{
Console.WriteLine("singleton1 i singleton2 to ta sama instancja.");
}
// Wywołujemy metodę Singletona
singleton1.DoSomething();
Console.ReadLine();
}
}
Efekt? W konsoli zobaczysz komunikat, że to ta sama instancja, a następnie komunikat z metody DoSomething().
Eager vs. Lazy – dwa podejścia do Singletona
1. Eager initialization (wczesna inicjalizacja)
• Instancja tworzona jest w momencie załadowania klasy (np. przy starcie aplikacji).
• Łatwiejsza implementacja, ale może niepotrzebnie zajmować zasoby, jeśli w danej sesji aplikacji nigdy nie użyjemy Singletona.
public sealed class EagerSingleton
{
private static readonly EagerSingleton _instance = new EagerSingleton();
private EagerSingleton()
{
}
public static EagerSingleton Instance => _instance;
}
2. Lazy initialization (późna inicjalizacja)
• Instancja tworzona jest dopiero w momencie pierwszego wywołania.
• Często spotykana w aplikacjach o dużym rozmiarze, gdzie zasoby powinny być alokowane tylko w razie potrzeby.
• Wymaga kontroli wielowątkowości (blokady lock) lub gotowych mechanizmów w .NET (np. Lazy<T>).
Implementacja Singletona z Lazy<T>
W .NET możesz skorzystać z klasy Lazy<T>, która sama troszczy się o bezpieczne tworzenie obiektów w aplikacjach wielowątkowych. Przykład:
public sealed class LazySingleton
{
private static readonly Lazy<LazySingleton> _instance
= new Lazy<LazySingleton>(() => new LazySingleton());
private LazySingleton()
{
// Ewentualna logika inicjująca
}
public static LazySingleton Instance => _instance.Value;
public void DoSomething()
{
Console.WriteLine("Lazy singleton is working!");
}
}
Takie rozwiązanie jest bardzo eleganckie i rekomendowane w wielu scenariuszach .NET.
Kiedy używać Singletona?
• Dostęp do zasobu globalnego – np. loger, cache, menedżer konfiguracji.
• Przechowywanie stanu – kiedy naprawdę potrzebujesz jednego, wspólnego stanu w aplikacji.
• Komunikacja zewnętrzna – np. klasa sterująca połączeniem do zewnętrznego urządzenia.
Zachowaj jednak ostrożność – nadużywanie Singletona może prowadzić do trudnego w utrzymaniu kodu, problemów z testowaniem (można stosować wzorzec Dependency Injection, który bywa łatwiejszy w testowaniu) czy do zbyt dużego sprzężenia w aplikacji.
Podsumowanie
Singleton to jeden z pierwszych wzorców projektowych, z jakim stykają się początkujący programiści. Zapewnia istnienie tylko jednej instancji klasy i udostępnia globalny dostęp do niej. Mimo to, należy go używać z rozwagą, ponieważ zbyt częste sięganie po singletony może utrudniać późniejszą rozbudowę i testowanie aplikacji.
Jeśli chcesz poszerzyć swoją wiedzę na temat wzorców projektowych, a także nauczyć się praktycznych umiejętności programowania w C#, serdecznie zapraszam Cię do mojego szkolenia online "Zostań Programistą .NET". Znajdziesz w nim nie tylko omówienie klasycznych wzorców, ale także praktyczne projekty, w których zobaczysz, jak efektywnie korzystać z platformy .NET.
Daj znać w komentarzu, jakie jest Twoje doświadczenie z wzorcem Singleton i w jakich sytuacjach stosujesz go najczęściej!
To wszystkie na dzisiaj. Jeżeli taki artykuł Ci się spodobał, to koniecznie dołącz do mojej społeczności – darmowe zapisy, gdzie będziesz również miał dostęp do dodatkowych materiałów i przede wszystkim bonusów. Do zobaczenia w kolejnym artykule.