El patrón Singleton en C#



El patrón de diseño Singleton fue descrito en el libro Dessign Patterns, Elements of Reusable Object-Oriented Software escrito por cuatro autores también conocidos como GoF (“Gang of four”).

La primera implementación que apareció del patrón, la del libro de GoF, es una implementación no segura en aplicaciones multi-hilo (not thread safe).

Es por ello que a lo largo del tiempo han ido apareciendo sucesivas implementaciones “thread safe” con diferentes pros y contras. En este artículo sólo mostraré implementaciones “thread safe”.

Definición

Singleton es un patrón de diseño del tipo creacional cuyo propósito es garantizar la existencia de una sola instancia de una clase. Además el acceso a esa única instancia tiene que ser global.

En C# la instrucción que contiene la palabra “new” sólo se puede ejecutar una vez, así nos aseguramos que sólo existe una instancia.

Implementación Thread-Safe y Not Lazy
 

public sealed class Singleton
{
    private readonly static Singleton _instance = new Singleton();

    private Singleton()
    {
    }

    public static Singleton Instance
    {
        get
        {
            return _instance;
        }
    }
}

Claves para no olvidar cómo crear este patrón:

  1. Constructor privado
  2. Campo estático privado con inicialización directa (inline)
  3. Propiedad estática pública que devuelve el campo instanciado

De este modo, al iniciarse la aplicación se ejecuta una sola vez la instrucción “new Singleton()” y la instancia queda creada para el resto de vida de la aplicación.

Mediante la propiedad “Instance” se puede acceder a la instancia desde cualquier parte del código.

Por ejemplo de este modo:

   Singleton.Instance.MetodoDeLaClase()

Esta implementación es Not Lazy, eso significa que la instancia se crea siempre al iniciar la aplicación, independientemente de que la vayamos a utilizar.

Seria Lazy si la instancia se crease en el momento que la utilizáramos por primera vez.

La versión Not Lazy es la más simple, la más utilizada y la más recomendada, pero si quieres conocer todas las implementaciones del patrón en C# aquí tienes este link a un capítulo del libro C# in Depth dónde se explican hasta seis implementaciones:

http://csharpindepth.com/Articles/General/Singleton.aspx

¿Es malo utilizar el patrón Singleton?

Si buscas Singleton en StackOverflow, a día de hoy, mayo 2017, la segunda pregunta más votada y la segunda en relevancia es:

Q: What is so bad about singletons?

Parafraseando a Brian Button:

  1. Esconden las dependencias de tu aplicación en tu código, en lugar de exponerlas a través de las interfaces.
     
  2. Violan el principio de responsabilidad única (SRP: Single Reponsability Principle) porque tienen dos responsabilidades: 1) controlar su ciclo de vida, 2) realizar el trabajo para el que se han diseñado.
     
  3. Implícitamente generan código acoplado. Esto dificulta la creación de “Moks” en tests unitarios.
     
  4. Si la clase tiene estado, ese estado es global y está presente durante todo el ciclo de vida de la aplicación. Lo cual es otro duro golpe a los test unitarios, puesto que los resultados podrían variar dependiendo del orden de ejecución de los tests unitarios.

Un ejemplo para entender el primer punto.

Ejemplo usando Singleton:

public class ServicioPago
{
    public void Ejecutar()
    {
       // Lógica antes de realizar el pago
       // ...
       PasarelaBancaria.Instancia.RealizarPago();
    }
}

Vs. exponiendo interfaces:

public class ServicioPago
{
    private readonly IPasarelaBancaria _pasarela;
 
    public ServicioPago (IPasarelaBancaria  pasarela)
    {
        _pasarela = pasarela;
    }
 
    public void Ejecutar()
    {
        // Lógica antes de realizar el pago
        // ...
        _pasarela.RealizarPago();
    }
}

En el primer ejemplo, si queremos probar la función Ejecutar acabaremos realizando un pago en una pasarela bancaria real, de producción o de test, da lo mismo.

En el segundo ejemplo, si pasamos al servicio una clase concreta de IPasarelaBancaria que sea un "Mock", podremos testear la función sin miedo a que se estén realizando pagos reales. 

Como norma general intento minimizar el uso del patrón Singleton en mi código, prefiero inyectar la dependencia a través del constructor o de la función.

Cuando está “aceptado” utilizar el patrón Singleton

En Loggers. Principalmente porque su existencia no afecta al resultado de la aplicación. Tanto si el Logger está activado como desactivado el resultado no varía.

La clave está en que la información fluye de la aplicación al logger, pero no a la inversa.

Misko Hevery lo explica con mucho más detalle: http://misko.hevery.com/2008/08/25/root-cause-of-singletons/

Más allá de los Loggers

Sin embargo hay más casos en los que utilizar Singletons puede ser aceptable.

Imagina esta serie de servicios a los que se les inyecta las dependencias en el constructor:

public ConstructorServicio1 (
          IDependencia1 dependencia1, 
          IDependencia2 dependencia2, 
          IDependencia3 dependencia3)
{
 ...
}
public ConstructorServicio2 (
          IDependencia1 dependencia1, 
          IDependencia2 dependencia2)
{
 ...
}
public ConstructorServicio3 (
           IDependencia1 dependencia1, 
           IDependencia4 dependencia4, 
           IDependencia5 dependencia5)
{
  ...
}



Si la mayoría de servicios de la aplicación comparten una misma dependencia, como en este caso la Dependencia1, utilizar el patrón Singleton te podría ahorrar mucho código redundante.

Creando la Dependencia1 como Singleton los constructores quedarían así:

public ConstructorServicio1 (
          IDependencia2 dependencia2, 
          IDependencia3 dependencia3)
{
 ...
}

public ConstructorServicio2 (
          IDependencia2 dependencia2)
{
...
}

public ConstructorServicio3 (
          IDependencia4 dependencia4, 
          IDependencia5 dependencia5)
{
 ...
}

Si existe una dependencia utilizada por la mayoría de servicios y de capas de la aplicación es una buena candidata para declararla como Singleton.

Cómo uso Singleton en mi código

Una manera de obtener los mismos resultados que con Singleton pero con mayor flexibilidad es añadiendo una función estática de inicialización. Por ejemplo así:

public static class Logger
{
        private static ILogger _logger;

        public static void Inicializar(ILogger logger)
        {
            _logger = logger;
        }

        public static void Log(LogEntrada logEntrada)
        {
            if(_logger == null)
                throw new Exception("No se ha inicializado el Logger");

            _logger.Log(logEntrada);
        }
}


Este código actúa de la misma manera que un Singleton, pero con la flexibilidad de poder inyectarle una dependencia.

De este modo, en el inicio de cada una de las aplicaciones cliente que utilizan el servicio se puede inicializar el Logger como convenga:

// En una aplicación de esctiorio
Logger.Inicializar(new RichTextBoxLogger(textBoxControl));

// En los tests unitarios
Logger.Inicializar(new TestLogger());

// En una web Asp.Net
var logger4Net = LogManager.GetLogger(typeof(Programa));
XmlConfigurator.Configure();
Logger.Inicializar(new WebLogger(logger4Net));

Singletons de Dominio (o Core, Modelo, etc)

En el artículo Singleton Vs Dependency Injection de Vladimir Khorikov, además de comparar ambos patrones, también describe una implementación de Singleton para la capa de Dominio que me parece muy interesante. Hasta ahora nunca la he utilizado, pero aquí queda por si es necesaria:

public class Organization
{
    public static Organization Instance { get; private set; }
 
    private Organization(int id, string name)
    {
        /* … */
    }
 
    public static void Init(int id, string name)
    {
        Instance = new Organization(id, name);
    }
}

Referencias:




Quizá algun día empiece a enviar una newsletter, si te gustaría recibirla subscríbete aquí

Archivo