
Singleton en C#
Singleton es un patrón de diseño creacional que garantiza que tan solo exista un objeto de su tipo y proporciona un único punto de acceso a él para cualquier otro código.
El patrón tiene prácticamente los mismos pros y contras que las variables globales. Aunque son muy útiles, rompen la modularidad de tu código.
No se puede utilizar una clase que dependa del Singleton en otro contexto. Tendrás que llevar también la clase Singleton. La mayoría de las veces, esta limitación aparece durante la creación de pruebas de unidad.
Complejidad:
Popularidad:
Ejemplos de uso: Muchos desarrolladores consideran el patrón Singleton un antipatrón. Por este motivo, su uso está en declive en el código C#.
Identificación: El patrón Singleton se puede reconocer por un método de creación estático, que devuelve el mismo objeto guardado en caché.
Singleton ingenuo
Es muy fácil implementar un Singleton descuidado. Tan solo necesitas esconder el constructor e implementar un método de creación estático.
La misma clase se comporta de forma incorrecta en un entorno multihilo. Los múltiples hilos pueden llamar al método de creación de forma simultánea y obtener varias instancias de la clase Singleton.
Program.cs: Ejemplo conceptual
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: Resultado de la ejecución
Singleton works, both variables contain the same instance.
Singleton con seguridad en los hilos
Para arreglar el problema, debes sincronizar hilos durante la primera creación del objeto Singleton.
Program.cs: Ejemplo conceptual
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: Resultado de la ejecución
FOO
FOO
¿Quieres más?
Existen variedades aún más especiales del patrón Singleton en C#. Consulta este artículo para saber más: