![Singleton](/images/patterns/cards/singleton-mini.png?id=914e1565dfdf15f240e766163bd303ec)
Singleton w języku C#
Singleton to kreacyjny wzorzec projektowy gwarantujący istnienie tylko jednego obiektu danego rodzaju. Udostępnia też pojedynczy punkt dostępowy do takiego obiektu z dowolnego miejsca w programie.
Singleton charakteryzuje się prawie takimi samymi zaletami i wadami jak zmienne globalne i chociaż jest bardzo poręczny, to jednak psuje modularność kodu.
Nie można przenieść klasy zależnej od Singletona i użyć jej w innym kontekście bez równoczesnego przeniesienia tego drugiego. To ograniczenie zazwyczaj ujawnia się na etapie tworzenia testów jednostkowych.
Złożoność:
Popularność:
Przykłady użycia: Wielu twórców oprogramowania uważa Singleton za antywzorzec, przez to jego wykorzystanie w kodzie C# maleje.
Identyfikacja: Singleton można rozpoznać po statycznej metodzie kreacyjnej zwracającej jakiś obiekt którego instancja jest przechowywana w pamięci podręcznej.
Implementacja naiwna
Łatwo jest zaimplementować wzorzec Singleton niechlujnie — wystarczy ukryć konstruktor i zaimplementować statyczną metodę kreacyjną.
Ta sama klasa będzie działać nieprawidłowo w środowisku wielowątkowym — różne wątki mogą wywołać metodę kreacyjną w tym samym momencie, otrzymując wiele instancji klasy Singleton.
Program.cs: Przykład koncepcyjny
using System;
namespace RefactoringGuru.DesignPatterns.Singleton.Conceptual.NonThreadSafe
{
// The Singleton class defines the `GetInstance` method that serves as an
// alternative to constructor and lets clients access the same instance of
// this class over and over.
// EN : The Singleton should always be a 'sealed' class to prevent class
// inheritance through external classes and also through nested classes.
public sealed class Singleton
{
// The Singleton's constructor should always be private to prevent
// direct construction calls with the `new` operator.
private Singleton() { }
// The Singleton's instance is stored in a static field. There there are
// multiple ways to initialize this field, all of them have various pros
// and cons. In this example we'll show the simplest of these ways,
// which, however, doesn't work really well in multithreaded program.
private static Singleton _instance;
// This is the static method that controls the access to the singleton
// instance. On the first run, it creates a singleton object and places
// it into the static field. On subsequent runs, it returns the client
// existing object stored in the static field.
public static Singleton GetInstance()
{
if (_instance == null)
{
_instance = new Singleton();
}
return _instance;
}
// Finally, any singleton should define some business logic, which can
// be executed on its instance.
public void someBusinessLogic()
{
// ...
}
}
class Program
{
static void Main(string[] args)
{
// The client code.
Singleton s1 = Singleton.GetInstance();
Singleton s2 = Singleton.GetInstance();
if (s1 == s2)
{
Console.WriteLine("Singleton works, both variables contain the same instance.");
}
else
{
Console.WriteLine("Singleton failed, variables contain different instances.");
}
}
}
}
Output.txt: Wynik działania
Singleton works, both variables contain the same instance.
Singleton z bezpieczeństwem wątków
Aby pozbyć się wyżej wymienionego problemu, trzeba zsynchronizować wątki w momencie pierwszego tworzenia obiektu Singleton.
Program.cs: Przykład koncepcyjny
using System;
using System.Threading;
namespace Singleton
{
// This Singleton implementation is called "double check lock". It is safe
// in multithreaded environment and provides lazy initialization for the
// Singleton object.
class Singleton
{
private Singleton() { }
private static Singleton _instance;
// We now have a lock object that will be used to synchronize threads
// during first access to the Singleton.
private static readonly object _lock = new object();
public static Singleton GetInstance(string value)
{
// This conditional is needed to prevent threads stumbling over the
// lock once the instance is ready.
if (_instance == null)
{
// Now, imagine that the program has just been launched. Since
// there's no Singleton instance yet, multiple threads can
// simultaneously pass the previous conditional and reach this
// point almost at the same time. The first of them will acquire
// lock and will proceed further, while the rest will wait here.
lock (_lock)
{
// The first thread to acquire the lock, reaches this
// conditional, goes inside and creates the Singleton
// instance. Once it leaves the lock block, a thread that
// might have been waiting for the lock release may then
// enter this section. But since the Singleton field is
// already initialized, the thread won't create a new
// object.
if (_instance == null)
{
_instance = new Singleton();
_instance.Value = value;
}
}
}
return _instance;
}
// We'll use this property to prove that our Singleton really works.
public string Value { get; set; }
}
class Program
{
static void Main(string[] args)
{
// The client code.
Console.WriteLine(
"{0}\n{1}\n\n{2}\n",
"If you see the same value, then singleton was reused (yay!)",
"If you see different values, then 2 singletons were created (booo!!)",
"RESULT:"
);
Thread process1 = new Thread(() =>
{
TestSingleton("FOO");
});
Thread process2 = new Thread(() =>
{
TestSingleton("BAR");
});
process1.Start();
process2.Start();
process1.Join();
process2.Join();
}
public static void TestSingleton(string value)
{
Singleton singleton = Singleton.GetInstance(value);
Console.WriteLine(singleton.Value);
}
}
}
Output.txt: Wynik działania
FOO
FOO
Chcesz wiedzieć więcej?
Jest więcej specjalnych rodzajów wzorca Singleton w C# i poniższy artykuł je opisuje: