Anda di halaman 1dari 20

El Patrón Singleton

Contenido
1. Introducción
2. El patrón Singleton
2.1 Definición del patrón
2.2 Breve Discusión
2.2.1 Singletonitis
2.2.2 Significado de la palabra Singleton
2.3 Ejemplo "No Software"
2.4 Ejemplos en .NET Framework
2.5 Ejemplos de Código
2.5.1 Ejemplo sencillo: implementación del GoF
2.5.1.1 Modificación del Ejemplo del GoF usando Propiedades
2.5.1.2 Problemas en ambientes multihilo
2.5.2 Primer mejora: thread safety
2.5.2.1 La sentencia lock
2.5.3 Double-Check Locking
2.5.3.1 Double-Check Locking y Java
2.5.4 Utilización de Facilidades del .NET Framework
2.5.5 Singleton sin la función/propiedad "Instancia"
3. Singleton y los Patrones de Fabricación
3.1 Fábrica Concreta como Singleton
3.1.1 Single Responsibility Principle
3.2 Exponiendo una Fábrica Concreta mediante un Singleton
3.3 Haciendo más flexible el ejemplo anterior con Reflection
4. Ejemplo de Aplicación: Un caché de parámetros
5. Referencias

1. Introducción
El Singleton es quizás el más sencillo de los patrones que se presentan en el
catálogo del GoF (disponible en el libro Patrones de Diseño [GoF95] y analizado
previamente en "Patrones y Antipatrones: una introducción" [Welicki05]).
Es también uno de los patrones más conocidos y utilizados. Su propósito es
asegurar que sólo exista una instancia de una clase.
En este artículo analizaremos en profundidad este patrón, exponiendo sus
fortalezas y debilidades. Presentaremos distintas opciones de implementación en
C#, haciendo hincapié en su correcto funcionamiento en entornos multihilos. Luego
relacionaremos los conceptos de este artículo con los patrones de fabricación que
hemos estudiado en la entrega anterior.
Finalmente, mostraremos un ejemplo de implementación de este patrón para crear
una caché de parámetros de configuración.

Principio de la página

2. El patrón Singleton
El patrón Singleton garantiza que una clase sólo tenga una instancia y proporciona
un punto de acceso global a ésta instancia.

2.1.Definición del patrón


A continuación presentaremos una versión reducida de la plantilla de este patrón.
Para una versión completa, consultar el libro del GoF [GoF95].

Intención
Garantiza que una clase sólo tenga una instancia y proporciona un punto de acceso
global a ella.

Problema
Varios clientes distintos precisan referenciar a un mismo elemento y queremos
asegurarnos de que no hay más de una instancia de ese elemento.

Solución
Garantizar una única instancia.

Figura 1: Diagrama OMT de Singleton, tomado del libro del GoF.

Participantes

Singleton
Define una operación Instancia que permite que los clientes accedan a su única
instancia. Instancia es una operación de clase (static en C# y shared en VB .NET).

Puede ser responsable de crear su única instancia.

Aplicabilidad

Usar cuando:

Deba haber exactamente una instancia de una clase y ésta deba ser accesible a los
clientes desde un punto de acceso conocido.
La única instancia debería ser extensible mediante herencia y los clientes deberían
ser capaces de utilizar una instancia extendida sin modificar su código.

Consecuencias

Acceso controlado a la única instancia. Puede tener un control estricto sobre cómo y
cuando acceden los clientes a la instancia.
Espacio de nombres reducido. El patrón Singleton es una mejora sobre las variables
globales.
Permite el refinamiento de operaciones y la representación. Se puede crear una
subclase de Singleton.

Permite un número variable de instancias. El patrón hace que sea fácil cambiar de
opinión y permitir más de una instancia de la clase Singleton.

Más flexible que las operaciones de clase (static en C#, Shared en VB .NET).

Resumen 1 - Vista simplificada y resumida del patrón Singleton, tomado de [GoF] y


[DPE].

2.2. Breve Discusión


El patrón Singleton asegura que exista una única instancia de una clase. A primera
vista, uno puede pensar que pueden utilizarse clases con miembros estáticos para
el mismo fin. Sin embargo, los resultados no son los mismos, ya que en este caso la
responsabilidad de tener una única instancia recae en el cliente de la clase. El
patrón Singleton hace que la clase sea responsable de su única instancia, quitando
así este problema a los clientes.
Adicionalmente, si todos los métodos de esta clase son estáticos, éstos no pueden
ser extendidos, desaprovechando así las capacidades polimórficas que nos proveen
los entornos orientados a objetos.
El funcionamiento de este patrón es muy sencillo y podría reducirse a los siguientes
conceptos:

• Ocultar el constructor de la clase Singleton, para que los clientes no puedan


crear instancias.
• Declarar en la clase Singleton una variable miembro privada que contenga la
referencia a la instancia única que queremos gestionar.

Proveer en la clase Singleton una función o propiedad que brinde acceso a la única
instancia gestionada por el Singleton. Los clientes acceden a la instancia a través
de esta función o propiedad.

Estas reglas se cumplen en todas las implementaciones del Singleton,


independientemente de los recaudos que deban tomarse para soportar la correcta
ejecución en entornos multihilo.

El ciclo de vida de los Singleton es un aspecto importante a tener en cuenta. En


Patterns Hatching [Vlissides98] John Vlissides se plantea y estudia en profundidad el
problema de "quién y cómo mata a un Singleton?", bajo el sugerente título de "To
Kill a Singleton".

2.2.1. SINGLETONITIS
En Refactoring to Patterns [Kerievsky04] se presenta el término Singletonitis,
refiriéndose a "la adicción al patrón Singleton". Este término aparece en la
motivación del refactoring "Inline Singleton", cuyo objetivo es remover los
Singletons innecesarios en una aplicación. Dado que el Singleton es quizás el patrón
más sencillo del GoF a veces es sobreutilizado y muchas veces en forma incorrecta.

¿Esto quiere decir que los Singletons son malos y no hay que usarlos?
Definitivamente no. Pero como todo, debe utilizarse en su justa medida y en el
contexto adecuado.

2.2.2. SIGNIFICADO DE LA PALABRA SINGLETON


Como curiosidad es interesante mencionar que la palabra singleton significa en
inglés "un conjunto que contiene un solo miembro".
Para clarificar aun más la raíz etimológica de este patrón, se incluyen los
significados (en inglés) encontrados para este término en el diccionario:

The playing card that is the only card in a suit held in a bridge hand as initially
dealt.

Set containing a single member.


A single object (as distinguished from a pair).

2.3. EJEMPLO "NO SOFTWARE"


En [Duell97] se presenta el siguiente ejemplo:
The office of the President of the United States is a Singleton. The United States
Constitution specifies the means by which a president is elected, limits the term of
office, and defines the order of succession. As a result, there can be at most one
active president at any given time. Regardless of the personal identity of the active
president, the title, "The President of the United States" is a global point of access
that identifies the person in the office.

En la Figura 2 se muestra un diagrama UML del ejemplo comentado arriba.

Figura 2: Ejemplo del mundo real del patrón Singleton, tomado de [Duell97]. Volver
al texto.

2.4. EJEMPLOS EN .NET FRAMEWORK


El Singleton se utiliza en forma extensiva en .NET. Un ejemplo de implementación
de este patrón puede encontrarse en Remoting.
Existen tres tipos de objetos que pueden configurarse para servir como objetos
remotos de .NET. El tipo de objeto puede ser elegido en función de los requisitos de
la aplicación. Los tipos de objeto son Single Call, Singleton y Client-Activated
Objects (CAO).

En este contexto, los Singleton son objetos que sirven a múltiples clientes y
comparten datos almacenando información de estado entre las distintas
invocaciones. Son útiles en escenarios donde los datos deben ser compartidos en
forma explícita entre clientes y/o cuando el overhead de creación y mantenimiento
de los objetos es sustancial.

2.5. EJEMPLOS DE CÓDIGO


En esta sección presentaremos varias opciones de implementación de este patrón
en .NET. Para escribir los ejemplos utilizaremos C#, aunque son aplicables y pueden
ser traducidos a cualquier otro lenguaje soportado por .NET (VB,.NET,J#,etc).

2.5.1. EJEMPLO SENCILLO: IMPLEMENTACIÓN DEL GoF


Esta forma de implementación es la que se presenta en el libro Design Patterns
[GoF95] y es quizás la más sencilla de todas. En el bloque de código a continuación
se muestra una traducción literal del ejemplo del GoF a C#.

public class Singleton


{
private static Singleton instance = null;

private Singleton() {}

public static Singleton GetInstance()


{
if (instance == null)
instance = new Singleton();

return instance;
}
}

Código 1 - Traducción literal del ejemplo del GoF a C#. Es importante aclarar que
esta implementación no funciona correctamente en entornos multihilo.

En este ejemplo hemos utilizado una función para dar acceso a la instancia del
Singleton, pero también puede utilizarse una propiedad (como se muestra en la
sección 2.5.1.1).

2.5.1.1. MODIFICACIÓN DEL EJEMPLO DEL GoF USANDO PROPIEDADES


A continuación, mostraremos una versión del patrón Singleton modificada,
utilizando una característica de .NET: las propiedades.

public class Singleton


{
private static Singleton instance = null;

protected Singleton() {}

public static Singleton Instance


{
get
{
if (instance == null)
instance = new Singleton();

return instance;
}
}
}

Código 2 - Ejemplo de implementación del Singleton usando propiedades. En este


caso, hemos modificado el ejemplo original utilizando una propiedad en lugar de
una clase, aprovechando las características de .NET. Es importante aclarar que esta
implementación no funciona correctamente en entornos multihilo.

El uso de propiedades hace más cómoda la utilización del Singleton. Por ejemplo,
para acceder a una función de un Singleton llamada MiFunción se usa
Singleton.Instance.MiFuncion() en lugar de Singleton.GetInstance().MiFuncion.

Para los siguientes ejemplos de este artículo utilizaremos una propiedad de sólo
lectura en lugar de una función para obtener la instancia del Singleton.

2.5.1.2. PROBLEMAS EN AMBIENTES MULTIHILO


Si estamos en un ambiente de un solo hilo (single-thread) esta implementación es
suficiente. En contrapartida, tiene serios problemas en entornos multihilo (multi-
thread) dado que, debido a cuestiones de sincronización y concurrencia, puede
crearse más de una instancia del miembro instance. ¿Cómo puede ser esto posible?
Imaginemos que dos hilos evalúan la condición instance == null y en ambos casos
es verdadera. En este caso, ambos hilos crearán la instancia, violando el propósito
del patrón Singleton.

Ahora bien, ¿Es esto un problema? Puede o no serlo... [DPE01]

Si el Singleton es absolutamente stateless (es decir, no mantiene ningún tipo de


estado) puede no ser un problema.

Si estamos en C++, se puede producir un memory leak, dado que sólo se va a


eliminar uno de los objetos aunque hayamos creado dos.

Si el Singleton es statefull (mantiene estado) se pueden producir errores sutiles. Por


ejemplo, si se modifica el estado del objeto en el constructor, pueden producirse
inconsistencias, dado ese código de inicialización se ejecuta más de una vez.

Tomemos como ejemplo de esto último un Singleton que implementa un contador.


Imaginemos que el constructor inicializa el contador a 0. Si se producen dos
creaciones, se inicializará dos veces el contador. Quizás en la segunda
inicialización, una instancia ya había incrementado su contador, pero debido a la
ejecución de ese código de inicialización, ese incremento se perderá.

Los problemas que se producen a raíz de esto pueden ser muy difíciles de detectar.
La creación dual suele producirse en forma intermitente e incluso puede no suceder
(no es determinista).

En la Figura 3 intentaremos ilustrar una de las formas en que puede presentarse


este problema, usando como base el ejemplo presentado en el Código 2. En este
caso, dos hilos solicitan la instancia a través de la función GetInstance, pero ésta
todavía no ha sido creada. Por lo tanto, en los dos casos se procede a la creación de
la única instancia.

Las dos columnas a la izquierda (Thread 1 y Thread 2) representan los hilos de


ejecución y muestran el código C# que se ejecuta en cada momento. La columna
de la derecha (Valor de Instance) muestra el valor de instance luego de que se
ejecuta cada línea de código.
Figura 3: Representación gráfica de los problemas de sincronización en la
implementación del Singleton de Código1 y Código2, Inspirada en [FF04]. Volver al
texto.

2.5.2. Primer mejora: thread safety


En este ejemplo, mejoramos un poco la situación anterior, haciéndola segura para
ambientes multihilo.

public class Singleton


{
public sealed class Singleton
{
private static Singleton instance = null;
private static readonly object padlock = new object();

private Singleton() {}

public static Singleton Instance


{
get
{
lock(padlock)
{
if (instance == null)
instance = new Singleton();

return instance;
}
}
}
}
}

Código 3 - Ejemplo básico de implementación de Singleton para ambientes


multihilo. En este caso, la utilización de recursos es ineficiente, dado que siempre
se hace un lock sobre todo el código de la propiedad/función, aun cuando la
instancia ya ha sido creada. En la sección 2.5.3 se presenta una solución para este
problema.

El ejemplo presentado arriba permite la ejecución segura en entornos multihilo,


aunque acarrea graves problemas de rendimiento, dado que todas las llamadas a la
propiedad instance son serializadas (todo el código del método está dentro de una
cláusula lock).

2.5.2.1. La sentencia lock


La sentencia lock se utiliza para asegurar que un bloque de código se ejecuta hasta
ser completado sin interrupciones. La sentencia lock es un envoltorio delgado sobre
las llamadas a Monitor.Enter() y Monitor.Exit(). Los monitores soportan bloqueos
exclusivos, lo cual permite que sólo un hilo a la vez acceda al bloqueo. El monitor
permite asociar un bloqueo con cualquier objeto del sistema. El método
Monitor.Enter(object) adquiere un bloqueo para un objeto y lo bloquea, mientras
que Monitor.Exit(object) suelta el bloqueo obtenido anteriormente para que esté
disponible para otros clientes.

La sentencia lock realiza las siguientes tareas:


Obtiene un bloqueo exclusivo (mutual-exclusive lock) para un objeto.
Ejecuta un conjunto de sentencias.
Luego suelta el bloqueo.
Por lo tanto, este código:

lock(this)
{
// sentencias...
}

Código 4 - Sentencia lock

Es traducido internamente por el compilador a ...

Try
{
System.Threading.Monitor.Enter(this);

// sentencias...
}
finally
{
System.Threading.Monitor.Exit(this);
}

Código 5 - Traducción de la sentencia lock realizada por el compilador

El objeto que se usa en la sentencia lock refleja la granularidad en la que se debe


obtener el bloqueo. Si los datos que se deben proteger son un ejemplar de datos
(instancia) el bloqueo típico es this, aunque si el dato es un tipo de referencia, se
podría usar el ejemplar de referencia.

Si los datos que se deben proteger son estáticos, será necesario bloquear usando
un objeto de referencia estático y único.
Para esto, podemos añadir un campo estático de tipo object a la clase donde
queramos hacer el lock (como hemos hecho en el ejemplo de Singleton con el
objeto padLock).

2.5.3. Double-Check Locking


Double-check locking es un idioma ampliamente citado y eficiente para
implementar inicialización tardía
(lazy inicialization) en entornos multihilo. Tiene las siguientes características:

Se evitan bloqueos innecesarios envolviendo la llamada al new con otro testeo


condicional.

Soporte para ambientes multihilo.

Esta versión tiene mejor rendimiento que la anterior, dado que el bloqueo sólo es
necesario cuando se crea la instancia de instance. De esta forma, al incurrir en
menos bloqueos, obtenemos un mejor rendimiento.

Finalmente, notar la inclusión de la cláusula volatile en la declaración de la


instancia interna del Singleton. Esta cláusula inhibe opciones de reordenamiento y
optimización del compilador, que pueden producir resultados inesperados en
entornos multihilo. Para una explicación detallada de volatile, se recomienda ver el
capítulo 29 de [Gunnerson02] y el 8 de [Lowy02].
public sealed class Singleton
{
private static volatile Singleton instance = null;
private static readonly object padlock = new object();

private Singleton() {}

public static Singleton Instance


{
get
{
if (instance == null)
{
lock(padlock)
{
if (instance == null)
instance = new Singleton();
}
}

return instance;
}
}
}

Código 6 - Ejemplo de implementación del Singleton con Double-Check Locking en


C#

2.5.3.1. Double-Check Locking y Java


Este idioma (hemos tratado el concepto de idioma en el primer artículo de esta
serie [Welicki05]) tiene problemas al ser implementado en Java y por lo tanto no
funciona correctamente en entornos de ejecución basados en JVM (Java Virtual
Machine).
Una de las causas de este problema es la forma en que la máquina virtual de Java
gestiona la memoria. Para una explicación profunda y detallada de este problema,
se recomienda leer el artículo "The Double-Checked Locking is Broken Declaration"
[DCL].

Este problema no se hace extensivo a J# (el compilador de Java para .NET), dado
que el código resultante de su compilación (MSIL) es ejecutado por el CLR y éste no
tiene problemas para implementar el idioma Double-Check Locking.

2.5.4. Utilización de Facilidades del .NET Framework


A continuación, presentamos la versión más compacta y simple del patrón
Singleton. Esta versión, a pesar de su sencillez, funciona en ambientes multihilo
(multi-thread). A diferencia de las versiones anteriores, no utiliza la técnica de
instanciación tardía (lazy instantiation) y por lo tanto crea la instancia del objeto
Singleton inmediatamente.

public sealed class Singleton


{
public static readonly Singleton instance = new Singleton();
private Singleton() {}
}

Código 7 - Ejemplo de Singleton implementado aprovechando características de


.NET Framework. En este caso se toma partido la forma en que el CLR gestiona los
campos estáticos de las clases). Para una explicación profunda sobre como gestiona
el CLR los campos estáticos se recomienda leer el capítulo 3 de "Essential .NET"
[Box02]

2.5.5. Singleton sin la función/propiedad "Instancia"


En algunos casos, puede resultar incómodo exponer toda la funcionalidad de la
clase a través del método GetInstance. En estos casos, podemos llevar la gestión de
la instancia a los métodos de la interface pública.
El problema de este tipo de clases es que no pueden ser subclaseadas, dado que su
interface pública está basada en métodos estáticos.

En el bloque de código a continuación se muestra una clase que implementa un


contador utilizando un Singleton.

public sealed class Singleton


{
private int counter = 0;
private static volatile Singleton instance = null;
private static readonly object padlock = new object();

private Singleton() {}

public static Singleton Instance


{
get
{
if (instance == null)
{
lock(padlock)
{
if (instance == null)
instance = new Singleton();
}
}

return instance;
}
}

public int IncrementCounter()


{
return this.counter++;
}
}

Código 8 - Contador implementado con un Singleton. En este caso, para acceder a


la instancia es necesario utilizar la propiedad Instance.

En el ejemplo anterior, el método para incrementar el contador es utilizado por los


clientes en la siguiente forma:

Singleton.Instance.IncrementCounter();

Código 9 - Ejemplo de uso del contador creado en el Código 8.

A continuación, rescribiremos el ejemplo presentado en el código 8 para que no


utilice la propiedad instancia:

public sealed class Singleton


{
private int counter = 0;
private static volatile Singleton instance = null;
private static readonly object padlock = new object();

private Singleton() {}

public static int IncrementCounter()


{
if (instance == null)
{
lock(padlock)
{
if (instance == null)
instance = new Singleton();
}
}
return instance.counter++;
}
}

Código 10 - El mismo contador que implementamos en el código 8, pero en este


caso no tiene la propiedad Instance.
Al invocar el método estático, éste se encarga de la gestión de la instancia. Existen
formas más apropiadas de recubrir
el método instancia, como por ejemplo, el uso conjunto de la composición y
delegación de objetos.

El ejemplo de Singleton que se presenta arriba es utilizado por los clientes en la


siguiente forma:

Singleton.IncrementCounter();

Código 11 - Ejemplo de utilización del contador creado en el código 10. Notar que
no es necesario invocar a la propiedad instancia.

Los resultados que se obtienen al ejecutar ambos ejemplos son los mismos, aunque
cada uno tiene sus ventajas y desventajas:

El primer ejemplo es más incomodo para los usuarios (dado que deben acceder a la
instancia a través de la propiedad instance), pero la clase Singleton puede ser
heredada, extendida y redefinida posteriormente.

El segundo ejemplo es más cómodo para los usuarios (dado que no deben hacer
referencia a la instancia, de hecho, ni se dan cuenta que están tratando con un
Singleton), pero no puede ser redefinido posteriormente, dado que su interface se
compone de un conjunto de métodos estáticos.

¿Cuándo es conveniente una opción en lugar de la otra? Esa decisión queda a


criterio del arquitecto o diseñador de la solución en cuestión. Como regla general,
podemos decir que la segunda opción puede ser aplicable para interfaces muy
estables y que sepamos que no serán redefinidas por otras clases posteriormente.
Si optamos por la segunda opción, es buena idea marcar a la clase Singleton como
sealed, lo cual indica que no puede ser redefinida.

Principio de la página

3. Singleton y los Patrones de Fabricación


En el artículo anterior, Patrones de Fabricación, presentamos varios patrones de
fabricación, a saber, Abstract Factory,
Factory Method y Simple Factory.
En el artículo anterior vimos como se relacionaban estos patrones. Concretamente,
hemos visto como el Abstract Factory se implementa utilizando Factory Method. En
el libro del GoF se establece también una relación entre Abstract Factory y
Singleton. La relación es que "una fábrica concreta suele ser un Singleton" [GoF95].
En la Figura 3 se muestra la relación entre estos patrones.

Figura 3: Relaciones existentes en el catálogo del GoF entre Singleton, Abstract


Factory y Factory Method. Volver al texto.

En esta sección mostraremos cómo implementar esta relación, tomando como


punto de partida el modelo de objetos y código de ejemplo para Abstract Factory
presentado en el artículo anterior [Welicki05b] y combinándolo con los nuevos
conceptos presentados en este nuevo articulo.

3.1.FÁBRICA CONCRETA COMO SINGLETON


En el libro del GoF se presenta la relación entre estos dos patrones diciendo que
"una fábrica concreta suele ser un Singleton". El ejemplo de código a continuación
muestra cómo implementar esta relación. En este caso, hemos transformado a la
Fábrica Concreta de elementos de UI de Windows en un Singleton (esta clase es
parte del ejemplo de implementación de Abstract Factory del artículo Patrones de
Fabricación: Fábricas de Objetos [Welicki05b])

public class SingletonWindowsWidgetFactory


{
private static volatile SingletonWindowsWidgetFactory instance;
private static readonly object padlock = new object();

private SingletonWindowsWidgetFactory() {}

public static SingletonWindowsWidgetFactory Instance


{
get
{
if (instance == null)
{
lock(padlock)
{
if (instance == null)
instance = new SingletonWindowsWidgetFactory();
}
}

return instance;
}
}

public Window CreateWindow()


{
return new WindowsWindow();
}

public Scrollbar CreateScrollbar()


{
return new WindowsScrollbar();
}
}

Código 12 - Implementación de una Fábrica Concreta combinada con Singleton


Los métodos de creación de la fábrica concreta del ejemplo presentado arriba
(SingletonWindowsWidgetFactory) se invocan de la siguiente forma:

Scrollbar theScrollbar =
SingletonWindowsWidgetFactory.Instance.CreateScrollbar();

Window theWindow =
SingletonWindowsWidgetFactory.Instance.CreateWindow();

Código 13 - Invocación de los métodos de creación de la Fábrica Concreta


utilizando el Singleton.

Podemos tener un Singleton por cada Fábrica Concreta (tantos como decidamos
implementar). Por lo tanto, si tenemos más de una fábrica concreta disponible,
podemos incurrir en el problema de consistencia presentado en el artículo anterior
cuando estudiamos el patrón Simple Factory [Welicki05b].

3.1.1. Single Responsibility Principle


El Single Responsibility Principle (en adelante SRP) establece que "una clase debe
tener una única responsabilidad". Se entiende por responsabilidad motivos por los
que ésta pueda cambiar.

El ejemplo presentado en bloque de código 12 viola este principio, dado que la


clase SingletonWindowsWidgetFactory tiene la responsabilidad de implementar la
Fábrica Concreta y el Singleton.

Es importante tener en cuenta que SRP es un principio abstracto de diseño y no una


ley. Por lo tanto, que no se cumpla no es condición determinante de que un diseño
sea malo o incorrecto. De hecho, la mayoría de las implementaciones del patrón
Singleton vulneran este principio, dado que la clase Singleton implementa una serie
de funcionalidades además del aseguramiento de la existencia de una única
instancia.

3.2. Exponiendo una Fábrica Concreta mediante un Singleton


En el siguiente ejemplo de código, exponemos una Fábrica Concreta a través de un
Singleton. El Singleton contiene una referencia a la fábrica concreta que se desea
exponer. De esta forma, no estamos violando el SRP, dado que la Fábrica Concreta
(IWidgetFactory) tiene la responsabilidad de crear instancias de objetos de una
familia y el Singleton (SingletonWidgetFactory) tiene la responsabilidad de asegurar
que sólo exista una instancia de la fábrica concreta.

public sealed class SingletonWidgetFactory


{
private static volatile IWidgetFactory internalFactory;
private static readonly object padlock = new object();

private SingletonWidgetFactory() {}

public static IWidgetFactory Instance


{
get
{
if (internalFactory == null)
{
lock(padlock)
{
if (internalFactory == null)
internalFactory = new WindowsWidgetFactory();
}
}
return internalFactory;
}
}
}

Código 14 - Combinación entre Singleton y Abstract Factory. La clase


SingletonWidgetFactory retorna una instancia de la fábrica concreta adecuada para
crear la familia de productos en el sistema. En este caso, solo hay una instancia de
una fábrica concreta para toda la aplicación. Adicionalmente, estamos siguiendo el
SRP, dado que la única responsabilidad de esta clase es asegurar que exista una
única instancia de una fábrica concreta.

3.3. Haciendo más flexible el ejemplo anterior con Reflection


En el ejemplo anterior (4.2), nos asegurábamos la existencia de una única Fabrica
Concreta de un tipo específico. Para cambiar el tipo de la fábrica concreta, debemos
modificar el código (la línea donde hacemos el new) y especificar el tipo de clase
concreta que queremos crear (en el artículo anterior habíamos creado dos tipos,
WindowsWidgetFactory y MacWidgetFactory). Para que esta modificación se haga
efectiva es necesario recompilar y volver a desplegar.

Esta situación puede ser evitada utilizando las capacidades reflectivas de .NET. En
el ejemplo siguiente, mostraremos cómo puede hacerse esto usando las facilidades
de System.Reflection. Para seleccionar el tipo de Fabrica Concreta usaremos un
parámetro en el fichero de configuración App.config, al cual llamaremos
ConcreteFactorType (el nombre ha sido elegido en forma totalmente arbitraria).
Este parámetro contiene el nombre del tipo que queremos usar para nuestra fábrica
concreta.
Es importante destacar que en este caso, si el tipo no implementa IWidgetFactory
se producirá una excepción (que no es controlada en el ejemplo).

using System;
using System.Configuration;

public sealed class SingletonDynamicWidgetFactory


{
private static volatile IWidgetFactory internalFactory;
private static readonly object padlock = new object();

/// <summary>
/// Constructor. Recupera el nombre del tipo de FabricaConcreta
/// de un fichero de configuracion
/// </summary>
private SingletonDynamicWidgetFactory() {}

public static IWidgetFactory Instance


{
get
{
if (internalFactory == null)
{
lock(padlock)
{
if (internalFactory == null)
{
// obtengo el nombre del tipo de fabrica a instanciar
string typeName =
ConfigurationSettings.AppSettings["ConcreteFactoryType"];

// creo el objeto dentro de este ensamblado


Type t = Type.GetType(typeName);
internalFactory = (IWidgetFactory)Activator.CreateInstance(t);
}
}
}

// retorno la instancia de la factoria


return internalFactory;
}
}
}

Código 15 - En este caso, hemos hecho más flexible el ejemplo presentado en el


código 14 permitiendo que se configure el tipo de la fábrica concreta que queremos
utilizar. Por lo tanto, podemos cambiar de fábrica concreta sin modificar el código
del Singleton que expone la única instancia.

En el fragmento a continuación se muestra un ejemplo del fichero de configuración


donde se establece el parámetro que determina el tipo de fábrica a crear.

<appSettings>
<add
key="ConcreteFactoryType"
value="SingletonDemo.WindowsWidgetFactory,SingletonDemo"/>
</appSettings>

Código 16 - Ejemplo de fragmento del fichero de configuración donde se especifica


el tipo de la fábrica concreta. Este valor se compone del nombre del tipo y el
nombre del ensamblado separados por una coma.

Principio de la página

4. Ejemplo de Aplicación: Un caché de parámetros


En esta sección mostraremos un escenario de implementación de este patrón. Para
esto, construiremos una caché de parámetros de configuración. El funcionamiento
de la caché es el siguiente:

El cliente de la caché solicita el valor de un parámetro

La caché busca el valor solicitado en su tabla de datos.

Si lo encuentra, lo retorna al cliente.

Si no lo encuentra:

Busca la información solicitada en el fichero de configuración (utilizando


System.Configuration).

Si la encuentra, la añade a su tabla de datos para que en las peticiones sucesivas


no tenga que volver a buscarla.

Retorna el valor al cliente.

La caché esta implementada como un Singleton. Los clientes acceden a la instancia


a través de la propiedad Instance.
Esta propiedad está implementada utilizando Double-Check Locking (explicado en
la sección 2.5.3). Adicionalmente, los accesos a la tabla interna de datos se
serializan mediante bloqueos (usando la sentencia lock). Por lo tanto, esta caché
puede ser utilizada en ambientes multihilo.
/// <summary>
/// Clase que aloja la informacion de configuracion.
/// Es un Singleton. Implementa tambien Template Method
/// </summary>
public class ConfigurationDataCache
{
protected static ConfigurationDataCache instance = null;
protected static readonly object padlock = new object();
private Hashtable data = null;

/// <summary>
/// Indizador. Recupera un valor del cache
/// de valores de configuracion
/// </summary>
public string this[string key]
{
get
{
return this.GetValue(key);
}
}

/// <summary>
/// Constructor. Inicializa la coleccion
/// interna de datos
/// </summary>
protected ConfigurationDataCache()
{
data = new Hashtable();
}

/// <summary>
/// Recupera un valor de un repositorio de configuracion.
/// Como esta marcado "virtual", puede ser redefinido
/// por las c lases hijas
/// </summary>
/// <remarks> "Operacion Primitiva" en el patron Template Method</remarks>
/// <param name="key">Clave a recuperar</param>
/// <returns>Valor. Null si no encuentra el valor para la clave</returns>
protected virtual string GetDataFromConfigRepository(string key)
{
// recupero el valor del elemento desde el fichero solicitado
return System.Configuration.ConfigurationSettings.AppSettings[key];
}

/// <summary>
/// Guarda los datos en el cache interno
/// </summary>
/// <param name="key">Clave de valor a guardar</param>
/// <param name="val">Valor a guardar</param>
private void StoreDataInCache(string key, string val)
{
lock (instance.data.SyncRoot)
{
// si el elemento ya esta en la lista de datos...
if (instance.data.ContainsKey(key))
{
// lo quito
instance.data.Remove(key);
}
// y lo vuelvo a añadir
instance.data.Add(key, val);
}
}

/// <summary>
/// Retorna un valor de la coleccion interna de datos
/// </summary>
/// <remarks> "Template Method" en el patron Template Method</remarks>
/// <param name="key">Clave del valor</param>
/// <param name="defaultValue">Valor default (si no se encuentra)</param>
/// <returns>Valor del parametro</returns>
public string GetValue(string key)
{
// variable con el valor de retorno
string ret = null;

// si el dato esta en el cache...


if (instance.data.ContainsKey(key))
{
// almaceno en la variable de retorno el valor del cache
ret = instance.data[key].ToString();
}
else // si no esta en el cache...
{
// recupero el parametro del repositorio de valores de configuracion
ret = this.GetDataFromConfigRepository(key);

// si lo ha encontrado, lo almaceno en el cache


if (ret != null)
this.StoreDataInCache(key, ret);
}

// retorno el valor del parametro solicitado


return ret;
}

/// <summary>
/// Obtener la instancia unica (Singleton)
/// </summary>
/// <returns>Retorna la instancia</returns>
public static ConfigurationDataCache Instance
{
get
{
// implementacion de singleton thread-safe usando double-check locking
if (instance == null)
{
lock(padlock)
{
if (instance == null)
{
instance = new ConfigurationDataCache();
}
}
}

return instance;
}
}

/// <summary>
/// Retorna true si el repositorio de
/// parametros contiene la clave especificada
/// </summary>
/// <param name="key">Clave a buscar</param>
/// <returns>True si existe la clave</returns>
public bool Contains(string key)
{
return instance.data.ContainsKey(key);
}

/// <summary>
/// Limpia los datos de configuracion
/// </summary>
public void Clear()
{
lock(instance.data.SyncRoot)
{
instance.data.Clear();
}
}
}

Código 17 - Código fuente de la caché. Notar que los accesos a los recursos críticos
se realizan utilizando bloqueos.

A continuación, se muestra un fichero de configuración de ejemplo.

<?xml version="1.0" encoding="utf-8" ?>


<configuration>
<appSettings>
<add key="param1" value="Parametro 1"/>
<add key="param2" value="Parametro 2"/>
</appSettings>
</configuration>

Código 18 - Ejemplo de fichero de configuración.

El siguiente fragmento muestra un ejemplo de uso de la caché (código 17)


utilizando el fichero de configuración que hemos presentado arriba (código 18).

// Obtiene el valor de param1. En este caso, debe recuperarlo del fichero de


configuracion
string v1 = ConfigurationDataCache.Instance.GetValue("param1");

// Obtiene el valor de param1. En este caso, no es necesario ir al fichero de //


configuracion, dado
que ya lo tiene almacenado en el cache
string v2 = ConfigurationDataCache.Instance.GetValue("param1");

// Busca el valor de ParamInexistente. Como no existe, retorna null


string v3 = ConfigurationDataCache.Instance.GetValue("ParamInexistente");

// v1 y v2 deben ser referencias al mismo objeto


Debug.Assert(object.ReferenceEquals(v1, v2));

// v3 debe ser nulo


Debug.Assert(v3 == null);
Código 19 - Ejemplo de utilización de la caché. Notar cómo al solicitar un
parámetro que no existe en el fichero de configuración se obtiene null.
Adicionalmente, si solicitamos dos veces el mismo parámetro obtenemos el mismo
objeto.
Esta caché puede ser modificada para recuperar parámetros de una tabla de una
base de datos o de cualquier otro repositorio.
Como la versión actual utiliza también el patrón Template Method, podemos crear
muy fácilmente una caché que lea parámetros de una base de datos (o cualquier
otro repositorio) heredando esta clase y redefiniendo el método
GetDataFromConfigRepository (que hace las veces de Operación Primitiva en el
patrón Template Method.

En caso de redefinir la clase, debemos volver a crear los elementos (campos y


funciones) para gestión de la instancia única.
En el ejemplo de código a continuación mostramos como crear una caché que lea
parámetros de configuración a partir del ejemplo anterior.

public class ConfigurationDataCacheFromDB: ConfigurationDataCache


{
protected static new ConfigurationDataCacheFromDB instance = null;

/// <summary>
/// Obtener la instancia única (Singleton)
/// </summary>
/// <returns>Retorna la instancia</returns>
public static new ConfigurationDataCacheFromDB Instance
{
get
{
if (instance == null)
{
lock(padlock)
{
if (instance == null)
{
instance = new ConfigurationDataCacheFromDB();
}
}
}

return instance;
}
}

/// <summary>
/// Recupera un valor de un repositorio de configuracion.
/// Como esta marcado "override", esta redefiniendo el
/// comportamiento del método de la clase base
/// </summary>
/// <remarks>Rol "Operacion Primitiva" en el
/// patron Template Method</remarks>
/// <param name="key">Clave a recuperar</param>
/// <returns>Valor</returns>
protected override string GetDataFromConfigRepository(string key)
{
string ret;

// ...
// Obtener los datos de la base de datos
// ...

return ret;
}
}

Código 19 - Ejemplo de redefinición de la caché para leer la información desde una


base de datos. Notar que el cuerpo de la clase sólo tiene el método que hemos
redefinido y los elementos para la gestión de la instancia única.

Existen otras formas de redefinir un Singleton. Podríamos optar por el camino que
hemos utilizado en el ejemplo de combinación con los patrones de fabricación. Si
queremos que varios tipos de Singleton convivan, podemos crear un registro de
Singletons, que será a su vez un Singleton encargado de gestionar las instancias de
éstas clases (como se propone el libro del GoF). Podemos también optar por la
utilización del patrón Strategy (que será motivo de estudio en una artículo
posterior) para poder variar dinámicamente el algoritmo de recuperación de datos
del repositorio de información.

5. REFERENCIAS
• [Box02] Box, Don: Essential .NET, Volume 1: The Common Language
Runtime, Addison Wesley, 2002
• [DCL] Bacon David et al: The "Double-Checked Locking is Broken"
Declaration
• [DOTNET02] DOTNET Archives: The DOTNET Memory Model, 2002,
• [DPE01] Shalloway, Alan; Trott James : Design Patterns Explained : A New
perspective on Object Oriented Design, Pearson Education, 2001
• [Duell97] Duell, Michael: Non-software examples of software design patterns,
Object Magazine, July 1997, pp54
• [FF04] Freeman, Eric et al: Head First Design Patterns, O’Reilly, 2004
• [Fowler99] Fowler, Martin: Refactoring: Improving the Design of Existing
Code, Adisson Wesley, 1999.
• [GoF95] Gamma E., Helm, R., Johnson, R., Vlissides J.: Design
Patterns: Elements of Reusable Object Oriented Software, Addison
Wesley, 1995.
• [Gunnerson02] Gunnerson, Erich: A Programmer's Introduction to C#,
APress, 2002
• [Hejlsberg03] Hejlsberg, Anders et al: The C# Programming Language,
Addison-Wesley, 2003
• [Kerievsky04] Kerievsky, Joshua: Refactoring to Patterns, Addison-Wesley,
2004
• [Lowy02] Lowy, Juval: Programming .NET Components, O‘Reilly, 2002
• [MSDN02] Microsoft Patterns: Implementing Singleton in C#, 2002
• [Msft05]
• [MsftMonitor] Microsoft Coporation: Monitor Class
• [PPR04] C2 WikiWikiWeb: Portland Pattern Repository
• [Singleton] Implementing the Singleton Pattern in C#
• [SingletonEvil] C2 Wiki: Singletons are Evil, 2005
• [SingletonGood] C2 Wiki: Singletons are Good, 2005
• [Srinivasan01] Srinivasan, Paddy: An Introduction to Microsoft .NET Remoting
Framework, 2001
• [Townsend02] Townsend, Mark: Exploring the Singleton Design Pattern, 2002
• [Vlissides98] Vlissides, John: Pattern Hatching: Design Patterns Applied,
Addison Wesley, 1998
• [Welicki05] Welicki, León: Patrones y Antipatrones: una introducción, Revista
MTJ .Net, 2005
• [Welicki05b] Welicki, León: Patrones de Fabricación, Revista MTJ .Net, 2005
León Welicki es Profesor Asociado de Ingeniería Web en el Máster en Ingeniería del
Software de la Universidad Pontificia de Salamanca, Madrid, España; donde
actualmente está realizando el Doctorado en Ingeniería Informática, su tesis
doctoral trata sobre las Arquitecturas de Software y Paradigmas No Convencionales
para Ingeniería Web. Trabaja como Arquitecto de Software. Cuenta con más de 12
años de experiencia profesional en diversas áreas de la Ingeniería del Software.

Anda mungkin juga menyukai