viernes, 7 de febrero de 2014

Patrones de creación: Singleton

Este post corresponde a la serie Patrones de diseño - Patrones de creación

Singleton

"Asegura que una clase tiene una sola instancia, y provee un punto de acceso global a esta instancia."

El patrón Singleton es probablemente el más sencillo de los patrones definidos por GoF. Debido a esto es frecuentemente sobre utilizado y a veces de forma incorrecta. Esto no quiere decir que el Singleton sea un patrón malo que no deba utilizarse, sino que, debe usarse en su justa medida y en el contexto correcto.




Este es el diagrama UML del patrón Singleton:

Participantes:

  • Singleton. Defina el método Instance que permite a los clientes acceder a su única instancia. Instance es un método estático. Igualmente se puede implementar con una propiedad estática.
  • Puede ser el responsable de crear su propia instancia única.
El patrón Singleton garantiza que sólo exista una instancia de una clase. Si queremos una sola instancia de una clase, no podemos esperar que los clientes la instancien una sola vez.
Debe ser el Singleton el responsable de mantener una sola instancia, liberando a los clientes de la clase de esa responsabilidad.
Aunque a primera vista parece que podamos conseguir lo mismo con una clase con miembros estáticos, esta no sería una buena idea. Si usáramos miembros estáticos no se podrían extender mediante herencia y perderíamos la capacidad de polimorfismo que nos ofrecen los lenguajes orientados a objetos. 

Implementaciones

Sea cual sea la implementación de un Singleton, la mayoría siguen las siguientes pautas básicas:

  • Se oculta el constructor de la clase Singleton para que los clientes no puedan crear instancias. Es decir se declara el constructor como private o protected.
  • Se crea un campo privado que mantiene la referencia a la instancia única que se crea.
  • Se crea un método o propiedad pública estática que permite a los clientes acceder a la instancia única del Singleton.
Veamos un ejemplo. Supongamos que en nuestra aplicación queremos ofrecer un acceso fácil a la configuración de algunos parámetros. Por ejemplo el número de items por página que se mostraran en los listados. En usuario puede cambiar el número de items por página que quiere ver, pero debemos asegurarnos que en todas las funcionalidades de nuestra aplicación ese número es el mismo. Para esto implementaremos un patrón Singleton que representa la configuración y del que podemos obtener el número de items por página a mostrar.

Implementación básica

Una primera implementación básica sería la siguiente:

/// 
/// Clase del Singleton
/// 
public class Configuration
{
    private static Configuration instance;

    private Configuration()
    {
        // Se inicializa el valor a 10 por defecto.
        ItemsPerPage = 10;
    }

    /// 
    /// Propiedad que permite el acceso a la instancia
    /// unica de Singleton
    /// 
    public static Configuration Instance
    {
        get
        {
            // Instanciación tardía de la unica instancia
            // del Singleton. Se pospone la instanciación 
            // hasta la primera vez que se solicita.
            if (instance == null)
                instance = new Configuration();
            return instance;
        }
    }

    public int ItemsPerPage { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Items por página: {0}", Configuration.Instance.ItemsPerPage);

        Configuration.Instance.ItemsPerPage = 30;

        Console.WriteLine("Items por página: {0}", Configuration.Instance.ItemsPerPage);
        Console.ReadLine();
    }
}

Cómo podemos comprobar en el código anterior la clase Configuration cumple las dos premisas del Singleton:

  1. Es responsable de mantener la instancia única del Singleton (una instancia de sí misma).
  2. Provee a los clientes una forma de acceder a la instancia única del Singleton. En este caso se hace a través de la propiedad estática Instance.
Es importante entender bien que aunque la propiedad que permite acceder al Singleton (Instance) es estática, el Singleton no es estático sino una instancia (objeto).
Otro punto interesante de esta implementación es la instanciación tardía de la instancia única del Singleton. Esto es importante porque aunque en este ejemplo el Singleton es muy "ligero", es posible que sea una clase pesada y no es necesario instanciarla si no se usa.

Implementación sin la propiedad Instance 

Cuando los clientes acceden a un Singleton habitualmente lo hacen a través de una propiedad/método (en el ejemplo anterior la propiedad Instance). Sin embargo, podemos implementar un Singleton mediante propiedades y métodos estáticos que delegan internamente en la instancia única del Singleton. El siguiente es una refactorización del ejemplo anterior que evita el uso de la propiedad Instance:

public class Configuration
{
    private static Configuration instance;
    private int itemsPerPage;
    private Configuration()
    {
        // Se inicializa el valor a 10 por defecto.
        // IMPORTANTE: Establecer el campo privado. 
        // ItemsPerPage = 10; provocaría StackOverflow.
        itemsPerPage = 10;
    }

    public static int ItemsPerPage
    {
        get
        {
            return GetInstance().itemsPerPage;
        }
        set
        {
            GetInstance().itemsPerPage = value;
        }
    }

    private static Configuration GetInstance()
    {
        if (instance == null)
            instance = new Configuration();
        return instance;
    }
}

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Items por página: {0}", Configuration.ItemsPerPage);

        Configuration.ItemsPerPage = 30;

        Console.WriteLine("Items por página: {0}", Configuration.ItemsPerPage);
        Console.ReadLine();
    }
}

Esta implementación como hemos dicho usa una propiedad estática (ItemsPerPage) que gestiona internamente la instancia única del Singleton. Esto hace que los clientes accedan a las propiedades y métodos del Singleton sin tener que escribir continuamente Instance. Pero aun así, esto sigue siendo un Singleton. Seguimos manteniendo una instancia única y proveemos un acceso a ella (a través de los métodos/propiedades estáticos).
Un problema añadido es que en este caso es difícil extender el Singleton mediante la herencia ya que la interfaz del Singleton está compuesta por una serie de método y propiedades estáticas.

Personalmente prefiero una implementación donde aparezca la propiedad/método (Instance en el ejemplo) para acceder a la instancia única aunque no sea tan cómodo para los clientes del Singleton. Por un lado permite más fácilmente extender el Singleton mediante herencia; y por otro, los clientes al acceder a la instancia única a través de una propiedad o un método (Instance en el primer ejemplo) tienen más claro con qué tipo de objeto están tratando. 

Singleton en entornos multihilo (multi-thread)

Antes de entrar en materia de Singleton con multihilo quiero dar las gracias a Kyril Jardinico porque esta sección del post es sugerencia suya. Y ya que estamos, por ser tan fiel lector del blog y porque siempre hace comentarios sobre los post que me animan a seguir. Gracias Kyril.

Ante todo decir que explicar la programación en entornos multihilo evidentemente no entra dentro de las pretensiones de este post y probablemente tampoco sea yo la persona más indicada para explicar este tema en profundidad. Así que veremos lo justo para entender el problema desde el punto de vista del patrón Singleton.

Para que podamos entender el problema partiremos de la primera implementación que vimos del Singleton que repetiré a continuación:
/// 
/// Clase del Singleton
/// 
public class Configuration
{
    private static Configuration instance;

    private Configuration()
    {
        // Se inicializa el valor a 10 por defecto.
        ItemsPerPage = 10;
    }

    /// 
    /// Propiedad que permite el acceso a la instancia
    /// unica de Singleton
    /// 
    public static Configuration Instance
    {
        get
        {
            // Instanciación tardía de la unica instancia
            // del Singleton. Se pospone la instanciación 
            // hasta la primera vez que se solicita.
            if (instance == null)
                instance = new Configuration();
            return instance;
        }
    }

    public int ItemsPerPage { get; set; }
}

Si estamos en un entorno de un sólo hilo (single-thread) esta implementación no presenta ningún problema. Sin embargo en entornos multihilo nuestro problema llega cuando dos hilos ejecutan a la vez la línea if (instance == null) del método get de Instance. En ese momento ambos hilos evaluará la condición como true y cada hilo creará una instancia distinta del Singleton, y esa no es evidentemente la idea que queremos.
Veamos un diagrama que nos permite entender mejor el problema:


El diagrama anterior muestra cómo en un entorno multihilo la primera implementación de Singleton que hemos visto puede no ser suficiente, ya que bajo algunas circunstancias es posible que se creen varias instancias del Singleton. Los errores de este tipo son realmente muy difíciles de detectar y de depurar ya que no son deterministas, no se producirán siempre y será muy difícil recrear un contexto donde reproducirlos. Para evitar este problema debemos hacer una implementación que tenga en cuenta los entornos multihilo.

No obstante, antes de implementar un Singleton que funcione en entornos multihilo debemos preguntarnos si el problema que acabamos de ver es realmente un problema para nosotros o no. Es decir, ¿es un problema tener varias instancias de un Singleton? Pues eso dependerá del tipo de Singleton que tenemos:
  • Si el Singleton no mantiene el estado (stateless), es decir, si la propiedad ItemsPerPage no cambia nunca esto no es un problema grave. En el peor de los casos tendríamos varias instancias del Singleton pero todas con el mismo valor, lo que desde un punto de vista práctico es como tener una.
  • Si el Singleton debe mantener el estado. Es decir, si los valores de las propiedades del Singleton cambian como es el caso de ItemsPerPage en nuestro ejemplo; no podemos permitirnos tener una instancia del Singleton que diga que debemos mostrar 10 items por página y otra instancia que diga 25 items por página. En este caso si hay un contexto multihilo debemos hacer una implementación del Singleton que evite este problema.
Bien, veamos una primera aproximación a una implementación del Singleton que funcione en un entorno multihilo:

public class Configuration
{
    private static Configuration instance;
    private static readonly object objectlock = new object();

    private Configuration()
    {
        // Se inicializa el valor a 10 por defecto.
        ItemsPerPage = 10;
    }

    public static Configuration Instance
    {
        get
        {
            lock (objectlock)
            {
                if (instance == null)
                    instance = new Configuration();
                return instance;
            }
        }
    }

    public int ItemsPerPage { get; set; }
}

El ejemplo anterior soportará entornos multihilo ya que hace un lock siempre que se va a acceder a la instancia única del Singleton.

La sentencia lock asegura que un bloque de código se ejecutará sin interrupciones. Es decir, si un hilo comienza a ejecutar el bloque nos aseguramos que acabará de ejecutarlo antes de permitir entrar a otro hilo en el bloque. El objeto que usamos como parámetro en la sentencia lock determina la granularidad del bloqueo que se realiza. Para nuestro ejemplo como pretendemos bloquear una región estática debemos pasar como parámetro de la sentencia lock un objeto estático y único. Por eso el objeto objectlock se declara estático y se instancia en el mismo momento de declararlo.

Esta implementación no obstante tiene un grave problema de rendimiento ya que bloquear una región de código tiene un alto coste de recursos y en nuestro caso estamos bloqueando la región de acceso a la instancia del Singleton siempre; incluso aunque el Singleton ya esté instanciado.

Double-Check Locking

Para evitar este problema de rendimiento usaremos una técnica que realiza el bloqueo con una doble comprobación del null de la instancia única del Singleton. Veamos cómo es esto:

public class Configuration
{
    private static volatile Configuration instance;
    private static readonly object objectlock = new object();

    private Configuration()
    {
        // Se inicializa el valor a 10 por defecto.
        ItemsPerPage = 10;
    }

    public static Configuration Instance
    {
        get
        {
            if (instance == null)
            {
                lock (objectlock)
                {
                    if (instance == null)
                        instance = new Configuration();
                }
            }
            return instance;
        }
    }

    public int ItemsPerPage { get; set; }
}

Con esta implementación sólo se entra en el bloque lock cuando aún no se ha creado la instancia única del Singleton, con lo que es mucho más eficiente que la implementación anterior.

Creo que esta implementación no funciona correctamente en Java por la forma que la máquina virtual de Java trata la memoria, pero esto sólo lo cuento "de oidas" porque no tengo los conocimientos de Java suficientes para asegurar esto.

Una cosa a tener en cuenta es que ahora a la declaración de la instancia se le ha incluido la cláusula volatile. Esta cláusula evita optimizaciones del compilador al tratar la instancia. Su explicación se sale del ambito de este post, pero nos vale con quedarnos que agregando la cláusula volatile a la declaración de la instancia nos aseguramos de que la cpu no va a cachear el valor null del Singleton y en cuanto se instancia ese valor pasará a la memoria. Esto evitará posibles comportamientos no deseados en entornos multihilo.

Por último indicar que todos estos problemas del Singleton en entornos multihilos se producen por la instanciación tardía de la instancia única del Singleton. Si tenemos un Singleton poco pesado una posible solución al problema con el entorno multihilo sería la siguiente:

public class Configuration
{
    private static Configuration instance = new Configuration();

    private Configuration()
    {
        // Se inicializa el valor a 10 por defecto.
        ItemsPerPage = 10;
    }

    public static Configuration Instance
    {
        get
        {
            return instance;
        }
    }

    public int ItemsPerPage { get; set; }
}

En este caso no se realiza la instanciación tardía de la instancia única del Singleton, sino que se crea la instancia inmediatamente por lo que no tenemos ningún riesgo de que se instancien más de un Singleton en entornos multihilo.

Ampliación del 19 de Marzo de 2014
Son Gokū en un comentario sugiere el uso de Lazy para implementar un Singleton. Nos ha aportado un enlace muy interesante a este respecto. Os animo a que echéis un vistazo. Es importante tener en cuenta que Lazy sólo puede usarse a partir de la versión 4 del framework .NET

Con el patrón Singleton hemos acabado la serie Patrones de diseño - Patrones de creación.
En el próximo post empezaremos con los patrones estructurales.

7 comentarios:

  1. Muy bueno, sigue así!

    ResponderEliminar
  2. Muy bien explicado, y has hablado de un montón de cosas para ser el patrón "más sencillo"!
    Estaría bien que ampliaras o hicieras otro post sobre los "malos usos" que mencionas. Por ejemplo hablando del anti-patrón ServiceLocator, que en el último proyecto hubo algunos problemas, como el código "poco testable"
    Gracias por el curro que te pegas!

    ResponderEliminar
  3. Cada post que que escribes cosas nuevas que aprendo. Gracias

    ResponderEliminar
  4. Muy bien Miguel Ángel.
    Tengo ganas de que lleguen tus posts de patrones estructurales.

    ResponderEliminar
  5. Muchas gracias a todos por vuestros comentarios.
    Sigo con un problema en blogger que no me permite responder a cada comentario. Así que permitirme que responda aquí.

    Miguel Ángel Ocaña:
    Justo ServiceLocator es un patrón de los que hablaré. La idea es hacerlo en una próxima serie que hablará de los patrones de aplicación. ServiceLocator en concreto lo contó Martin Fowler como un patrón y ciertamente hoy en día mucha gente lo considera una mala práctica, un anti-patrón. Como ya te digo hablaremos de esto.
    En cuanto a anti-patrones en general creo que el momento correcto será en otra próxima serie (como ves tengo más ideas que tiempo) en la que hablaré de refactorización de código heredado. Aún no se si se llamará asi. Aún estoy dandole vueltas a cómo estructurarla.

    José Antonio Sánchez: Me alegra mucho que aprendas con los post. Realmente es la finalidad última de este lío en el que me he metido. Muchas gracias.

    Jaume: Primero gracias por ser tan fiel seguidor :o) Empezaré los patrones estructurales por el patrón Adapter. Espero que sea a final de semana, pero se podría retrasar hasta primeros de la semana que viene :o( Esta semana estoy hasta arriba y si no me da tiempo aprovecho el próximo finde para acabarlo.

    De nuevo muchas gracias a todos.

    ResponderEliminar
  6. Yo añadaría una explicación a la solución mas elegante, usando Lazy. Aquí puedes ver una explicación: http://geekswithblogs.net/BlackRabbitCoder/archive/2010/05/19/c-system.lazylttgt-and-the-singleton-design-pattern.aspx

    ResponderEliminar
    Respuestas
    1. Muchas gracias. Una solución interesante. He agregado un comentario en el post

      Eliminar