Professional Documents
Culture Documents
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.
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.
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).
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).
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.
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.
The playing card that is the only card in a suit held in a bridge hand as initially
dealt.
Figura 2: Ejemplo del mundo real del patrón Singleton, tomado de [Duell97]. Volver
al texto.
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.
private 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).
protected Singleton() {}
return instance;
}
}
}
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.
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).
private Singleton() {}
return instance;
}
}
}
}
}
lock(this)
{
// sentencias...
}
Try
{
System.Threading.Monitor.Enter(this);
// sentencias...
}
finally
{
System.Threading.Monitor.Exit(this);
}
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).
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.
private Singleton() {}
return instance;
}
}
}
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.
private Singleton() {}
return instance;
}
}
Singleton.Instance.IncrementCounter();
private Singleton() {}
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.
Principio de la página
private SingletonWindowsWidgetFactory() {}
return instance;
}
}
Scrollbar theScrollbar =
SingletonWindowsWidgetFactory.Instance.CreateScrollbar();
Window theWindow =
SingletonWindowsWidgetFactory.Instance.CreateWindow();
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].
private SingletonWidgetFactory() {}
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;
/// <summary>
/// Constructor. Recupera el nombre del tipo de FabricaConcreta
/// de un fichero de configuracion
/// </summary>
private SingletonDynamicWidgetFactory() {}
<appSettings>
<add
key="ConcreteFactoryType"
value="SingletonDemo.WindowsWidgetFactory,SingletonDemo"/>
</appSettings>
Principio de la página
Si no lo encuentra:
/// <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;
/// <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.
/// <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;
}
}
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.