Singleton con y sin Singleton

Ahora que están de moda los antipatrones, voy a explicar un patrón que es muy famoso pero que no me gusta absolutamente nada, no por el diseño del mismo, sino por los efectos laterales no deseados que genera. Por ello también voy a explicar otra forma de construirlo que me gusta más, aunque adolece de otras limitaciones.

Singleton es un patrón que nos obliga a tener una sola instancia de una clase, es decir, globalmente sólo podremos disponer de un objeto de ese tipo. El truco está en conseguir que, hagamos lo que hagamos, no podamos crear más de una instancia.

En lenguajes como C++ este patrón tiene menos utilidad que en C++/CLI, C# o VB.NET, ya que la forma clásica de hacer algo similar es tener una variable global accesible por todos los módulos, cosa de la que suele encargarse el enlazador de hacer.

Pero los lenguajes .NET adolecen de falta de variables globales; es decir, no puedes tener un objeto global y visible en toda la aplicación. Personalmente no entiendo este purismo tan quisquilloso, ya que los lenguajes .NET no son precisamente orientados a objetos puros, y menos aún con la especificación 3.0 de C#. Y si encima este hecho complica bastante ciertos desarrollos, el absurdo sube de nivel.

Pero es lo que hay.

La solución para tener un objeto global es disponer de una clase estática. Si bien a priori resulta una buena idea, en la práctica no lo es tanto. Independientemente del hecho de que los constructores estáticos en .NET no funcionan muy bien o, en otras palabras, tienen bastante bugs (al menos en la versión 2.0, aunque dudo mucho que lo hayan solucionado en los SP1), tenemos ciertas limitaciones, como la imposibilidad de que se llame a otro constructor estático de forma encadenada…

¿Y por qué comento esto? Pues porque el patrón Singleton utiliza una variable y un constructor estático. Veámoslo en C#:

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Text;
   4:  
   5: class Singleton
   6: {
   7:     public static Singleton Instancia=new Singleton();
   8:  
   9:     private Singleton() {}
  10:  
  11:     public int Numero;
  12:     public void DiNumero()
  13:     {
  14:         Console.WriteLine(Numero++.ToString());
  15:     }
  16: }
  17:  
  18: class Program
  19: {
  20:     static void Main(string[] args)
  21:     {
  22:         Singleton.Instancia.Numero = 33;
  23:         for (int i = 0; i < 10;i++)
  24:             Singleton.Instancia.DiNumero();
  25:     }
  26: }

Observamos que el truco está en definir un constructor privado. Al hacerlo, ya no podremos equivocarnos y crear varias instancias de esta clase, y la única forma de acceder al único objeto que pueda haber en toda la aplicación es mediante

   1: Singleton.Instancia

Si intentamos crearnos un objeto del tipo Singleton, el compilador protestará y nos dirá que el constructor no está accesible.

Si alguien quiere una explicación más en detalle, se puede pasar por la MSDN o por aquí.

¿Nadie ve el problema?

Pues hay dos. El primero ya lo hemos dicho: tenemos un objeto global al sistema, que es estático y por tanto sigue unas reglas algo diferentes, como la imposibilidad de hacer una llamada a otra variable estática que implemente su propio constructor estático dentro del constructor de nuestro Singleton… O en otras palabras: no podemos tener Singletones anidados, no al menos bajo .NET.

Pero el mayor problema es otro. Cuando se habla de Singleton, se pone un ejemplo con dos líneas de código y listo. Pero una clase de la Vida Real™ no es sencilla. Tendremos varios métodos miembro, así como variables. ¿Cómo las inicializamos? ¿En el constructor? ¡Pero si no tiene constructor al que podamos pasarle valores! ¿Las ponemos públicas? ¿Propiedades?

Una solución es la mostrada aquí:

   1: Singleton.Instancia.Numero = 33;
   2: for (int i = 0; i < 10;i++)
   3:     Singleton.Instancia.DiNumero();

Pero a mí al menos eso me parece una guarrería mayor, y más cuando haya varios datos miembro, como suele ser habitual en clases no triviales… La única cosa que podemos hacer es especificar valores por defecto en el constructor estático:

   1: private Singleton() { Numero = 50; }

Pero eso sólo alivia el problema, no lo soluciona.

Qué bonito sería que pudiéramos indicar algo como

   1: [Pattern(Singleton)]
   2: class Singleton
   3: {…

Y disponer de la clase como si tal cosa… Pero esa es otra guerra.

Otra aproximación

Veamos ahora una variante, que es la que yo implemento en mis propios programas. Primero el código:

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Text;
   4:  
   5: class MiSingleton
   6: {
   7:     private static int m_cuenta=0;
   8:  
   9:     private int m_numero;
  10:     public MiSingleton(int num)
  11:     {
  12:         m_cuenta++;
  13:         if (m_cuenta > 1)
  14:             throw new Exception("RFOG Singleton Exception");
  15:  
  16:         m_numero = num; 
  17:     }
  18:     public void DiNumero()
  19:     {
  20:         Console.WriteLine(m_numero++.ToString());
  21:     }
  22: }
  23: class Program
  24: {
  25:     static void Main(string[] args)
  26:     {
  27:         MiSingleton m = new MiSingleton(33);
  28:         for (int i = 0; i < 10;i++)
  29:             m.DiNumero();
  30:  
  31:         MiSingleton n=new MiSingleton(88);
  32:     }
  33: }

Aquí encontramos otros problemas y otras limitaciones, pero personalmente me gusta más así. Definimos una variable estática, un entero que nos servirá para contar cuántas instancias de esta clase tenemos.

Definimos un constructor público al que podremos pasarle cualquier número de parámetros que queramos, de hecho se trata de una clase normal y corriente que tiene un bloque de código concreto en el constructor:

   1: m_cuenta++;
   2: if (m_cuenta > 1)
   3:     throw new Exception("RFOG Singleton Exception");

La segunda vez que intentemos instanciar esta clase obtendremos una excepción. Aquí la limitación está en que nos daremos cuenta de nuestro error en tiempo de ejecución, no de compilación, lo que ciertamente es una desventaja seria pero que pierde fuerza cuando nos enfrentamos a las limitaciones y bugs de los constructores estáticos.

La segunda instanciación dentro del bloque de main() lanzará la excepción. La otra limitación a esto viene de la imposibilidad de tener variables globales y de que tengamos que pasar m siempre que queramos usarlo.

Lo ideal sería tener ambas opciones juntas, pero ciertamente dentro de .NET es imposible.

21 comentarios sobre “Singleton con y sin Singleton”

  1. Interesante post!!!

    Solo como comentario constructivo, para que si alguien usa tu implementación lo tenga en cuenta:

    Tu implementación no es ‘Thread Safe’, la implementación que usa el contructor estático si que lo es…

    Por cierto para lectores avispados ¿por qué la implementación que usa el constructor estático es ‘thread safe’ y la otra no?… ¿Cómo haríamos ‘thread safe’ la implementación de Rafael?

  2. Ontivero.. macho.. la de siempre para no variar.

    La de Rodrigo es de cajon macho… tu código no impide que se creen mas de una instancia porque no es Thread Safe…. y ese ++ no te garantiza nada.
    La segunda es que no me voy a meter en la definición del Singleton para no crear un polémica absurda pero…
    La tercera es que es un patron bien conocido y por lo tanto admitido, el problema es que abuses de el y lo uses cuando no tienes que usarlo, esto es el ANTIPATRON.
    La tercera tu implementación suena a la que también se hacía en C++ con Double Lock Checking, ( que es lo que comenta Rodri al final)…. pero mal echa

    Para no variar Rafael…. a ver si un dia nos volvemos de cena y nos damos de ostias 🙂 🙂 un poco
    Unai

  3. Lo que haz hecho no es un singleton puesto que podes crear tantas instancias como quieras.
    Eso de obtener una exception al crear un objeto porque ya existe otro no es buena idea. La idea es que no se pueda crear más de una instancia y no que un tester tenga que encontrar el error en tiempo de ejecución.
    Otra cosa importante, un singleton se inicializa una sola vez (si después se quiere se puede cambiar su estado) y es para tenerlo disponible globalmente, por lo que los problemas que planteas parece más adecuado implementar un pool para así reutilizar viejas instancias de objetos.
    Según el snipet que pusiste, una vez que se destruya una instancia ya no será posible crear otra a menos que creemos un destructor que decremente la variable m_cuenta! ummm esto es un asco.
    En cuanto a lo de:
    [Pattern(Singleton)]
    Eso ya existe en el ObjectBuilder Famework.
    Saludos

  4. Cachis… Menuda lluvia de críticas… 😛

    Vayamos por partes.

    Rodri: Efectivamente no es Thread Safe, pero no lo son nignuna de las dos opciones, tal y como se indica en los dos enlaces puestos dentro del texto.

    Unai: Anda con ojo, porque cuando nos veamos llevaré un buen surtido de «tejaícos», «flechicas» y asteriscos extra, y tu sólo tendrás puntos. 🙂 Por lo demás, lo dicho a Rodrigo. De todos modos, por mi puedes meter toda la polémica que quieras… 🙂

    Tocayo: Un rapapolvo.

    Sólo se puede crear una instancia excepto porque no es thread safe y sí que se podrían crear más a partir de otros hilos.

    Respecto a lo que comentas sobre detectar el error en tiempo de ejecución, estoy contigo y ya lo digo en el mismo texto.

    ¿Un pool de puertos serie? ¿Cómo se hace eso si sólo tienes 1 (por ejemplo) y varias partes del programa necesitan acceder a él? O haces un Singleton o pasas el objeto que representa el puerto serie.

    En lo del destructor tienes toda la razón del mundo, pero si estamos mimetizando un objeto global a nivel de aplicación el destructor sólo se llamará al finalizar ésta, y en los quioscos y demás máquinas ni siquiera eso…

    ¿Existe lo de [Pattern(Singleton)] y se puede hacer con clases normales dentro del .NET? ¿Dónde?

  5. Dejar un constructor público y lanzar una bonita excepción cuando lo utilizas dos o más veces no parece un comportamiento muy predecible, sino más bien una venganza contra tus compañeros 🙂

    Y el singleton es un patrón expresivo que resuelve una necesidad, si se utiliza cuando no existe esa necesidad pues…

    Lo de los constructores estáticos lo he encontrado interesante, no se si bugs es la palabra correcta pero es cierto que tienen un comportamiento bastante impredecible

  6. Otra vez metes la ZARPA tio… MIENTES COMO UN BELLACO 🙂

    El CLR te garantiza que static object a = new object() es Thread Safe tio!!!
    además la buena implementacion es

    public static readonly CLASE instance = new CLASE()..

    Te recomiendo que te leas ( ya se que no me lee nadie )

    http://geeks.ms/blogs/unai/archive/2007/02/08/double-checking-locking-volatile-y-dem-s-cool-codes.aspx

    de hace mucho tiempo y de paso que te leas CLR VIA C# de Jeffrey que precisamente se rie de este tema y te explica porque la inicializacion de estaticos es Thread Safe C# y no en C++

    P.D: Igual te llevas un susto con *, ^ y demás pérdidas de productividad 🙂

  7. La implementación del Singleton que has puesto de ejemplo nunca la habia visto y no me gusta nada 😛

    Como apuntan Rodrigo y Unai, el tener una única instancia en todo el dominio de aplicación puede traer como inconveniente que varios threads intenten instanciarla a la vez, con lo que tendrías dos (ó incluso varias) instancias. Además no hace falta que sea estático.

    La forma segura (con doble test) seria algo asi (de memoria):

    class mySingleton
    {
    private readonly Object locker = new Object();
    private volatile mySingleton instance;

    private mySingleton(){};

    public static mySingleton GetInstance()
    {
    if(instance!=null)
    {
    lock(locker)
    if(instance==null)
    instance = new mySingleton();
    }
    return instance;
    }
    }

    Ese es el patrón que tengo yo asumido por «bueno» de todo lo que he podido leer en internet 😛

    De todas formas, hay ejemplos de como realizar un Singleton que albergue varias instancias de forma thread safe en MSDN :

    http://www.microsoft.com/spanish/msdn/comunidad/mtj.net/voices/MTJ_4081/default.aspx#M255

    Un saludo.

  8. PD: Si, si hace falta que sea estático ó se indique GC.KeepAlive(instance) después de crearlo ó el GC se lo comerá cuando nadie lo este usando xD

    Fallo mio 😛

  9. Valeriano.. tu implementacion es la vieja, un double-lock-checking.. pero NO HACE FALTA en C# COÑEEEEEE eso solo les hace falta a los de C++ :-)… EN C# la inicialización del estático es Tread Safe, no perecosa pero Thread Safe…

    http://www.yoda.arachsys.com/csharp/singleton.html
    http://www.dofactory.com/Patterns/PatternSingleton.aspx
    http://aspadvice.com/blogs/oliviers_net_blog/archive/2006/07/25/Thread-safe-singleton-pattern-with-C_23003A00_-easy_2100_.aspx

    Cuanto más necesitais para convenceros de que esta garantizado que la inicializacion de estaticos es Thread Safe…..

    Unai

  10. Vaya, tengo 26 años y ya esoy obsoleto xD

    Lo que más me jode es que eso este en la web de Jon Skeet, uno de los bloggers que sigo asiduamente, alguna vez he charlado con él por email y cuya guia sobre multithreading me adoctrinó e incluso tengo impresa … y no lo haya visto !! … manda huevos.

    Gracias!!

  11. Además de los problemas que citas hay otro que sólo he visto tratado en un libro: ¿Cómo, cuándo y quién destruye el singleton?

    Supongo que en C# se encarga el recolector de basura, pero no me parece muy elegante. Como no conozco el C# te pongo la solución que yo utilizo en C++, por si la consideras interesante y es facilmente trasladable a C#

    Observarás que no hay contador de instancias y por lo tanto no hay necesidad de provocar ninguna excepción, ya que no hace falta poner ‘new’ en ningún sitio.

    Para finalizar. No entiendo lo de los singletones anidados, Con esta estructura puedes tener varios singletones (singleton_recurso_a, singleton_recurso_b, …) que se utilicen simultaneamente y se llamen entre ellos sin ningún problema.

    class singleton {
    public:
    // La única manera de acceder al singleton es mediante esta función
    // fíjarse que es una función estática
    static singleton *Instancia(void);

    // Por si hay que poner un valor inicial, o en cualquier
    // momento que quiera el usuario
    void Init(int n) { m_numero = n;}

    void DiNumero() { std::out < < m_numero++ << "n"; } private: int m_numero; singleton(void) : m_numero(1) {} }; singleton *singleton::Instancia(void) { // Al ser la variable estatica, C++ crea el objeto sólo la primera // vez que pasa por aqui. // Al ser la variable estática, C++ destruirá el objeto al salir del programa static singleton Singleton; // Las siguientes veces no crea nada y sólo devuelve la dirección // del objeto creado la primera vez, es una función rapidísima return &Singleton; } // El usuario no puede usar new en ningún momento, ni por ninguna razón void main(void) { // La manera de usarlo de manera esporádica singleton::Instancia()->DiNumero();
    singleton::Instancia()->DiNumero();
    singleton::Instancia()->DiNumero();

    // Otra manera de usarlo, si se tiene que utilizar el singleton
    // de manera repetitiva , por ejemplo en un bucle
    // Se ganar algo de tiempo de ejecución
    singleton *Singleton = singleton::Instancia();

    // Cambiamos el contador
    Singleton->Init(34);
    for (int i=0; i<10; i++)
    Singleton->DiNumero();
    } // Aqui se destruirá el singleton

  12. Valeriano: tu implementación no es thread-safe aún usando el double-lock-checking. Nada impediría al procesador reordenar algunas lecturas y escrituras que dieran como casualidad que eso te falle (por el modelo de memoria de .NET).

    Rafael: la mejor implementación es la de static, es sencilla y no tiene pegas 🙂 En el CLR via C# explican muy bien que pasa con las variables estáticas y demás inicializaciones y en que orden ocurren. Opino como la mayoría de los demás comentarios, el segundo singleton es una venganza :p (y es de puro C++ traducido a C#).

  13. Me sumo a los partidarios de la implementación «nativa» de .NET, en vez de la heredada de C++

    Rafael, recuerda que el patrón Singleton es necesidad en C++ por limitaciones de ese lenguaje, el órden de inicialización entre distintos módulos ( translation units me viene a la cabeza ) no está específicado. Corrígeme que desde el 2003 que dejé C++ por C# ando algo oxidado 😉

    PD: Deberíamos «corregir» las entradas en la Wikipedia(Español e Inglés al menos )

  14. Vaya lío que se ha montado…

    El código que muestras no es thread safe, como ya se ha indicado. Para hacerlo, necesitarías una región crítica usando lock() con un parámetro que evalúe siempre a lo mismo (y no, no vale hacer trampas con un typeof(…)).

    Y como bien indica Unai, no es necesario, y no es «usable». Porque el dejar el constructor público, para un objeto que no se puede construir… es algo peligroso. Generalmente el que crea el singleton sabe por qué lo ha creado. El que lo usa no tiene por qué. Y si no sabe que es un singleton, a la hora de usarlo, tirará felizmente una línea de código invocando a constructor y… Catacrocker!, que dirían en Muchachada Nui. Y como además en este mundo el código _no se prueba_, te lo encontrarás cuando tengas que entregar la aplicación… y en multitud de sitios, que no sabrás con certeza, ya que el código compila.

    No reinventemos la rueda (salvo que esta sea cuadrada).

    Saludos!

  15. Resumiendo resúmenes (y no vale tirarme las cosas a la cabeza):

    Primero. Mi versión de Singleton no os gusta. Pues bueno, a mi me va de cine y no tengo problemas con los elementos estáticos.

    Segundo. ¿Por qué no se ha de poder construir un Singleton? Es decir, yo quiero que mi Singleton arranque con un comportamiento diferente al de un constructor por defecto. ¿Qué hay en contra de eso?

    Tercero. Thread Safe. Según Unai y Rodrigo, los constructores estáticos son seguros en cuanto a hilos… Ni Helsjberg en su libro base lo dice, ni he podido encontrarlo en la MSDN. Veremos. Lo comprobaré cuando tenga algo de tiempo.

    Cuarto. ¿Por qué Singleton ha de usar por narices un elemento estático, con lo problemáticos que son. Se ha hablado de limitaciones del C++ con el tema del orden de incialización (cosa que si bien en principio es cierto, se puede ajustar tocando el mapa del linker), en este caso la limitación del .NET es la de no disponer de variables globales y tener que solucionarlo de mala manera con un elemento estático…

    Quinto. ¿Es mal uso de un Singleton envolver un puerto serie en una clase y que otras clases lo usen, puesto que cada puerto serie tiene que ser único en el sistema? ¿No es el limitar que sólo haya una instancia de una clase el motivo del patrón Singleton?

    Sexto. Tengo buen avío de «tejaícos» y «flechicas», y los estoy afilando (no lo dijo por nada, pero algunos deberían ir a alguna cena con coraza). 🙂 🙂

  16. Hoy cenando Octavio me dijo una frase que muy bien te sirve

    Si el tronco nace doblado no se puede enderezar!! .. 🙂 🙂

    Y no es por que lo digamos Yo o Rodrigo, solamente dices lo que te interesa capullin lo tienes en los enlaces y tambien en el libro de Richter por si te quieres leer algo bueno de vez en cuando 🙂

    Saludos
    Unai

  17. Creo que no es muy buena idea esta implementación del singleton pues si expones el constructor como público es porque permites que distintos clientes lo utilizen y si sólo le das una funcionalidad válida únicamente al primero los otros no van a poder hacer uso del mismo.
    La idea de este patrón: «Permite tener una única instancia de esta clase en el sistema, a la vez que se permite que todas las clases tengan acceso a esa instancia», con lo cual no cumples con la segunda condición, sólo el primer cliente que instancie tu clase va a poder utilizar dicho objeto, los demás no.

    Pero considero que la idea de que singleton no es una muy buena pues no sigue al pie de la letra las buenas prácticas de programación: «programa contra interfaces no contra implementaciones».
    Una alternativa a esto es determinar que quién se encargue de la política de instanciación de objetos no sea la misma clase sino una clase factoría, que se encargue de tener como restricción que sólo se devolverá una única instancia para determinada clase.
    Propongo como solución alternativa frameworks de inyección de dependencia que persiguen la idea de diseñar contra interfaces y no contra implementaciones y donde ellos mismos manejan la manera de instanciar dichas clases (singleton, no singleton, por sesiónhttp, etc). Como ejemplos de frameworks de inyección de dependencia hay varios: StructureMap, Spring, NUnity.

    Esta misma idea de utilizar frameworks de inyección de dependencia puede ser reemplazada por el uso de fábricas abstractas que tengan en cuenta la devolución de objetos singletons en ciertos casos, pero los frameworks previamente mencionados son una alternativa bastante buena a todo esto, pues ya tienen todo esto resuelto.

    Saludos desde Argentina…

    Martín Caballero

  18. Es comun en el uso de singleton un problema que es muy simple de solucionar pero no se encunetra en los ejemplos que he visto de singleton «No se puede obtener acceso al objeto eliminado.»

    la solucion es tan simple como: _Instancia.IsDisposed

    de esta forma:
    public static Clase Instancia()
    {
    if (_Instancia == null || _Instancia.IsDisposed)
    {
    lock (typeof(AddServicio))
    {
    _Instancia = new Clase();
    }
    }
    return _Instancia;
    }

  19. Hola Rafael!

    Se ha escrito mucho sobre los singletones cosas que se han olvidado:

    .Net si te garantiza que una llamada a un constructor estático es seguro frente a subprocesos, esto se garantiza por un patrón que .net usa llamado double lock, pero ahí una pequeña pega si en vez de crear el objeto del singleton directamente en la definición de la variable o definir tú mismo el constructor estático. Eso se llama beforefieldinit y creo que en .net 4 lo han cambiado.

    Se puede hacer un Singleton perezoso o se puede hacer un singleton que la primera vez que se llame al tipo se inicialice.
    Sobre la inicialización básica del tipo de Singletón si su construcción es muy compleja utiliza una factoría para crearla.
    Por lo demás escribí un artículo hace tiempo en geeks.ms y mi buen amigo Vicente también hizo lo mismo, miratelos.

    http://kartones.net/blogs/jadengine/archive/2009/05/20/el-patr-243-n-singleton-en-c.aspx
    http://geeks.ms/blogs/luisguerrero/archive/2009/05/23/como-implementar-un-singleton-concurrente.aspx

Responder a anonymous Cancelar respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *