Você está na página 1de 23

El Patrn Singleton

Por Len Welicki

Contenido
1. Introduccin 2. El patrn Singleton 2.1 Definicin del patrn 2.2 Breve Discusin 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 Cdigo 2.5.1 Ejemplo sencillo: implementacin del GoF 2.5.1.1 Modificacin 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 Utilizacin de Facilidades del .NET Framework 2.5.5 Singleton sin la funcin/propiedad "Instancia" 3. Singleton y los Patrones de Fabricacin 3.1 Fbrica Concreta como Singleton 3.1.1 Single Responsibility Principle 3.2 Exponiendo una Fbrica Concreta mediante un Singleton 3.3 Haciendo ms flexible el ejemplo anterior con Reflection 4. Ejemplo de Aplicacin: Un cach de parmetros 5. Referencias

1. Introduccin
El Singleton es quizs el ms sencillo de los patrones que se presentan en el catlogo del GoF (disponible en el libro Patrones de Diseo [GoF95] y analizado previamente en "Patrones y Antipatrones: una introduccin" [Welicki05]). Es tambin uno de los patrones ms conocidos y utilizados. Su propsito es asegurar que slo exista una instancia de una clase. En este artculo analizaremos en profundidad este patrn, exponiendo sus fortalezas y debilidades. Presentaremos distintas opciones de implementacin en C#, haciendo hincapi en su correcto funcionamiento en entornos multihilos. Luego relacionaremos los conceptos de este artculo con los patrones de fabricacin que hemos estudiado en la entrega anterior. Finalmente, mostraremos un ejemplo de implementacin de este patrn para crear una cach de parmetros de configuracin. Principio de la pgina

2. El patrn Singleton
El patrn Singleton garantiza que una clase slo tenga una instancia y proporciona un punto de acceso global a sta instancia.

2.1.Definicin del patrn A continuacin presentaremos una versin reducida de la plantilla de este patrn. Para una versin completa, consultar el libro del GoF [GoF95]. Intencin Garantiza que una clase slo 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 ms de una instancia de ese elemento. Solucin Garantizar una nica instancia.

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

Singleton


Aplicabilidad Usar cuando:

Define una operacin Instancia que permite que los clientes accedan a su nica instancia. Instancia es una operacin de clase (static en C# y shared en VB .NET). Puede ser responsable de crear su nica instancia.

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 debera ser extensible mediante herencia y los clientes deberan ser capaces de utilizar una instancia extendida sin modificar su cdigo.

Consecuencias

Acceso controlado a la nica instancia. Puede tener un control estricto sobre cmo y cuando acceden los clientes a la instancia. Espacio de nombres reducido. El patrn Singleton es una mejora sobre las variables globales. Permite el refinamiento de operaciones y la representacin. Se puede crear una subclase de

Singleton.

Permite un nmero variable de instancias. El patrn hace que sea fcil cambiar de opinin y permitir ms de una instancia de la clase Singleton. Ms flexible que las operaciones de clase (static en C#, Shared en VB .NET).

Resumen 1 - Vista simplificada y resumida del patrn Singleton, tomado de [GoF] y [DPE]. 2.2.Breve Discusin El patrn Singleton asegura que exista una nica instancia de una clase. A primera vista, uno puede pensar que pueden utilizarse clases con miembros estticos 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 patrn Singleton hace que la clase sea responsable de su nica instancia, quitando as este problema a los clientes. Adicionalmente, si todos los mtodos de esta clase son estticos, stos no pueden ser extendidos, desaprovechando as las capacidades polimrficas que nos proveen los entornos orientados a objetos. El funcionamiento de este patrn es muy sencillo y podra reducirse a los siguientes conceptos: 1. 2. 3. 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 funcin o propiedad que brinde acceso a la nica instancia gestionada por el Singleton. Los clientes acceden a la instancia a travs de esta funcin o propiedad.

Estas reglas se cumplen en todas las implementaciones del Singleton, independientemente de los recaudos que deban tomarse para soportar la correcta ejecucin 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 "quin y cmo mata a un Singleton?", bajo el sugerente ttulo de "To Kill a Singleton".

2.2.1.Singletonitis
En Refactoring to Patterns [Kerievsky04] se presenta el trmino Singletonitis, refirindose a "la adiccin al patrn Singleton". Este trmino aparece en la motivacin del refactoring "Inline Singleton", cuyo objetivo es remover los Singletons innecesarios en una aplicacin. Dado que el Singleton es quizs el patrn ms 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 ingls "un conjunto que contiene un solo miembro". Para clarificar aun ms la raz etimolgica de este patrn, se incluyen los significados (en ingls) encontrados para este trmino en el diccionario: 1. 2. 3. Tthe 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 patrn 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 implementacin de este patrn 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 funcin de los requisitos de la aplicacin. Los tipos de objeto son Single Call, Singleton y Client-Activated Objects (CAO). En este contexto, los Singleton son objetos que sirven a mltiples clientes y comparten datos almacenando informacin de estado entre las distintas invocaciones. Son tiles en escenarios donde los datos deben ser compartidos en forma explcita entre clientes y/o cuando el overhead de creacin y mantenimiento de los objetos es sustancial. 2.5.Ejemplos de Cdigo

En esta seccin presentaremos varias opciones de implementacin de este patrn 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: implementacin del GoF


Esta forma de implementacin es la que se presenta en el libro Design Patterns [GoF95] y es quizs la ms sencilla de todas. En el bloque de cdigo a continuacin se muestra una traduccin 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; } }

Cdigo 1 - Traduccin literal del ejemplo del GoF a C#. Es importante aclarar que esta implementacin no funciona correctamente en entornos multihilo. En este ejemplo hemos utilizado una funcin para dar acceso a la instancia del Singleton, pero tambin puede utilizarse una propiedad (como se muestra en la seccin 2.5.1.1).
2.5.1.1.Modificacin del Ejemplo del GoF usando Propiedades

A continuacin, mostraremos una versin del patrn Singleton modificada, utilizando una caracterstica 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; } }

Cdigo 2 - Ejemplo de implementacin del Singleton usando propiedades. En este caso, hemos modificado el ejemplo original utilizando una propiedad en lugar de una clase, aprovechando las caractersticas de .NET. Es importante aclarar que esta implementacin no funciona correctamente en entornos multihilo. El uso de propiedades hace ms cmoda la utilizacin del Singleton. Por ejemplo, para acceder a una funcin de un Singleton llamada MiFuncin se usa Singleton.Instance.MiFuncion() en lugar de Singleton.GetInstance().MiFuncion. Para los siguientes ejemplos de este artculo utilizaremos una propiedad de slo lectura en lugar de una funcin 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 implementacin es suficiente. En contrapartida, tiene serios problemas en entornos multihilo (multi-thread) dado que, debido a cuestiones de sincronizacin y concurrencia, puede crearse ms de una instancia del miembro instance. Cmo puede ser esto posible? Imaginemos que dos hilos evalan la condicin instance == null y en ambos casos es verdadera. En este caso, ambos hilos crearn la instancia, violando el propsito del patrn Singleton. Ahora bien, Es esto un problema? Puede o no serlo... [DPE01]

Si el Singleton es absolutamente stateless (es decir, no mantiene ningn tipo de estado) puede no ser un problema. Si estamos en C++, se puede producir un memory leak, dado que slo 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 cdigo de inicializacin se ejecuta mas 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. Quizs en la segunda inicializacin, una instancia ya haba incrementado su contador, pero debido a la ejecucin de ese cdigo de inicializacin, ese incremento se perder.

Los problemas que se producen a raz de esto pueden ser muy difciles de detectar. La creacin 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 Cdigo 2. En este caso, dos hilos solicitan la instancia a travs de la funcin GetInstance, pero sta todava no ha sido creada. Por lo tanto, en los dos casos se procede a la creacin de la nica instancia. Las dos columnas a la izquierda (Thread 1 y Thread 2) representan los hilos de ejecucin y muestran el cdigo 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 lnea de cdigo.

Figura 3: Representacin grfica de los problemas de sincronizacin en la implementacin del Singleton de Cdigo 1 y Cdigo 2, Inspirada en [FF04]. Volver al texto.

2.5.2.Primer mejora: thread safety


En este ejemplo, mejoramos un poco la situacin anterior, hacindola 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; } } } } }

Cdigo 3 - Ejemplo bsico de implementacin de Singleton para ambientes multihilo. En este caso, la utilizacin de recursos es ineficiente, dado que siempre se hace un lock sobre todo el cdigo de la propiedad/funcin, aun cuando la instancia ya ha sido creada. En la seccin 2.5.3 se presenta una solucin para este problema. El ejemplo presentado arriba permite la ejecucin segura en entornos multihilo, aunque acarrea graves problemas de rendimiento, dado que todas las llamadas a la propiedad instance son serializadas (todo el cdigo del mtodo est dentro de una clusula lock).
2.5.2.1.La sentencia lock

La sentencia lock se utiliza para asegurar que un bloque de cdigo 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 slo un hilo a la vez acceda al bloqueo. El monitor permite asociar un bloqueo con cualquier objeto del sistema. El mtodo 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: 1. 2. 3. Obtiene un bloqueo exclusivo (mutual-exclusive lock) para un objeto. Ejecuta un conjunto de sentencias. Luego suelta el bloqueo.

Por lo tanto, este cdigo:

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

Cdigo 4 - Sentencia lock Es traducido internamente por el compilador a ...

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

Cdigo 5 - Traduccin 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 tpico es this, aunque si el dato es un tipo de referencia, se podra usar el ejemplar de referencia. Si los datos que se deben proteger son estticos, ser necesario bloquear usando un objeto de referencia esttico y nico. Para esto, podemos aadir un campo esttico 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 inicializacin tarda (lazy inicialization) en entornos multihilo. Tiene las siguientes caractersticas:

Se evitan bloqueos innecesarios envolviendo la llamada al new con otro testeo condicional. Soporte para ambientes multihilo.

Esta versin tiene mejor rendimiento que la anterior, dado que el bloqueo slo es necesario cuando se crea la instancia de instance. De esta forma, al incurrir en menos bloqueos, obtenemos un mejor rendimiento. Finalmente, notar la inclusin de la clusula volatile en la declaracin de la instancia interna del Singleton. Esta clusula inhibe opciones de reordenamiento y optimizacin del compilador, que pueden producir resultados inesperados en entornos multihilo. Para una explicacin detallada de volatile, se recomienda ver el captulo 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; } } }

Cdigo 6 - Ejemplo de implementacin 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 artculo de esta serie [Welicki05]) tiene problemas al ser implementado en Java y por lo tanto no funciona correctamente en entornos de ejecucin basados en JVM (Java Virtual Machine). Una de las causas de este problema es la forma en que la mquina virtual de Java gestiona la memoria. Para una explicacin profunda y detallada de este problema, se recomienda leer el artculo "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 cdigo resultante de su compilacin (MSIL) es ejecutado por el CLR y ste no tiene problemas para implementar el idioma Double-Check Locking.

2.5.4.Utilizacin de Facilidades del .NET Framework


A continuacin, presentamos la versin ms compacta y simple del patrn Singleton. Esta versin, a pesar de su sencillez, funciona en ambientes multihilo (multi-thread). A diferencia de las versiones anteriores, no utiliza la tcnica de instanciacin tarda (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() {} }

Cdigo 7 - Ejemplo de Singleton implementado aprovechando caractersticas de .NET Framework. En este caso se toma partido la forma en que el CLR gestiona los campos estticos de las clases). Para una explicacin profunda sobre como gestiona el CLR los campos estticos se recomienda leer el captulo 3 de "Essential .NET" [Box02]

2.5.5.Singleton sin la funcin/propiedad "Instancia"


En algunos casos, puede resultar incmodo exponer toda la funcionalidad de la clase a travs del mtodo GetInstance. En estos casos, podemos llevar la gestin de la instancia a los mtodos de la interface pblica. El problema de este tipo de clases es que no pueden ser subclaseadas, dado que su interface pblica est basada en mtodos estticos. En el bloque de cdigo a continuacin 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++; } }

Cdigo 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 mtodo para incrementar el contador es utilizado por los clientes en la siguiente forma:

Singleton.Instance.IncrementCounter();

Cdigo 9 - Ejemplo de uso del contador creado en el Cdigo 8. A continuacin, rescribiremos el ejemplo presentado en el cdigo 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++; } }

Cdigo 10 - El mismo contador que implementamos en el cdigo 8, pero en este caso no tiene la propiedad Instance. Al invocar el mtodo esttico, ste se encarga de la gestin de la instancia. Existen formas ms apropiadas de recubrir el mtodo instancia, como por ejemplo, el uso conjunto de la composicin y delegacin de objetos. El ejemplo de Singleton que se presenta arriba es utilizado por los clientes en la siguiente forma:

Singleton.IncrementCounter();

Cdigo 11 - Ejemplo de utilizacin del contador creado en el cdigo 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 ms incomodo para los usuarios (dado que deben acceder a la instancia a travs de la propiedad instance), pero la clase Singleton puede ser heredada, extendida y redefinida posteriormente. El segundo ejemplo es ms cmodo para los usuarios (dado que no deben hacer referencia a la instancia, de hecho, ni se dan cuenta que estn tratando con un Singleton), pero no puede ser redefinido posteriormente, dado que su interface se compone de un conjunto de mtodos estticos.

Cundo es conveniente una opcin en lugar de la otra? Esa decisin queda a criterio del arquitecto o diseador de la solucin en cuestin. Como regla general, podemos decir que la segunda opcin puede ser aplicable para interfaces muy estables y que sepamos que no sern redefinidas por otras clases posteriormente. Si optamos por la segunda opcin, es buena idea marcar a la clase Singleton como sealed, lo cual indica que no puede ser redefinida. Principio de la pgina

3. Singleton y los Patrones de Fabricacin


En el artculo anterior, Patrones de Fabricacin, presentamos varios patrones de fabricacin, a saber, Abstract Factory, Factory Method y Simple Factory. En el artculo 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 tambin una relacin entre Abstract Factory y Singleton. La relacin es que "una fbrica concreta suele ser un Singleton" [GoF95]. En la Figura 3 se muestra la relacin entre estos patrones.

Figura 3: Relaciones existentes en el catlogo del GoF entre Singleton, Abstract Factory y Factory Method. Volver al texto. En esta seccin mostraremos cmo implementar esta relacin, tomando como punto de partida el modelo de objetos y cdigo de ejemplo para Abstract Factory presentado en el artculo anterior [Welicki05b] y combinndolo con los nuevos conceptos presentados en este nuevo articulo. 3.1.Fbrica Concreta como Singleton En el libro del GoF se presenta la relacin entre estos dos patrones diciendo que "una fbrica concreta suele ser un Singleton". El ejemplo de cdigo a continuacin muestra cmo implementar esta relacin. En este caso, hemos transformado a la Fbrica Concreta de elementos de UI de Windows en un Singleton (esta clase es parte del ejemplo de implementacin de Abstract Factory del artculo Patrones de Fabricacin: Fbricas 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(); } }

Cdigo 12 - Implementacin de una Fbrica Concreta combinada con Singleton Los mtodos de creacin de la fbrica concreta del ejemplo presentado arriba (SingletonWindowsWidgetFactory) se invocan de la siguiente forma:

Scrollbar theScrollbar = SingletonWindowsWidgetFactory.Instance.CreateScrollbar(); Window theWindow = SingletonWindowsWidgetFactory.Instance.CreateWindow();

Cdigo 13 - Invocacin de los mtodos de creacin de la Fbrica Concreta utilizando el Singleton. Podemos tener un Singleton por cada Fbrica Concreta (tantos como decidamos implementar). Por lo tanto, si tenemos ms de una fbrica concreta disponible, podemos incurrir en el problema de consistencia presentado en el artculo anterior cuando estudiamos el patrn 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 cdigo 12 viola este principio, dado que la clase SingletonWindowsWidgetFactory tiene la responsabilidad de implementar la Fbrica Concreta y el Singleton. Es importante tener en cuenta que SRP es un principio abstracto de diseo y no una ley. Por lo tanto, que no se cumpla no es condicin determinante de que un diseo sea malo o incorrecto. De hecho, la mayora de las implementaciones del patrn Singleton vulneran este principio, dado que la clase Singleton implementa una serie de funcionalidades adems del aseguramiento de la existencia de una nica instancia. 3.2.Exponiendo una Fbrica Concreta mediante un Singleton En el siguiente ejemplo de cdigo, exponemos una Fbrica Concreta a travs de un Singleton. El Singleton contiene una referencia a la fbrica concreta que se desea exponer. De esta forma, no estamos violando el SRP, dado que la Fbrica Concreta (IWidgetFactory) tiene la responsabilidad de crear instancias de objetos de una familia y el Singleton (SingletonWidgetFactory) tiene la responsabilidad de asegurar que slo exista una instancia de la fbrica 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; } } }

Cdigo 14 - Combinacin entre Singleton y Abstract Factory. La clase SingletonWidgetFactory retorna una instancia de la fbrica concreta adecuada para crear la familia de productos en el sistema. En este caso, solo hay una instancia de una fbrica concreta para toda la aplicacin. Adicionalmente, estamos siguiendo el SRP, dado que la nica responsabilidad de esta clase es asegurar que exista una nica instancia de una fbrica concreta. 3.3.Haciendo ms flexible el ejemplo anterior con Reflection En el ejemplo anterior (4.2), nos asegurbamos la existencia de una nica Fabrica Concreta de un tipo especfico. Para cambiar el tipo de la fbrica concreta, debemos modificar el cdigo (la lnea donde hacemos el new) y especificar el tipo de clase concreta que queremos crear (en el artculo anterior habamos creado dos tipos, WindowsWidgetFactory y MacWidgetFactory). Para que esta modificacin se haga efectiva es necesario recompilar y volver a desplegar. Esta situacin puede ser evitada utilizando las capacidades reflectivas de .NET. En el ejemplo siguiente, mostraremos cmo puede hacerse esto usando las facilidades de System.Reflection. Para seleccionar el tipo de Fabrica Concreta usaremos un parmetro en el fichero de configuracin App.config, al cual llamaremos ConcreteFactorType (el nombre ha sido elegido en forma totalmente arbitraria). Este parmetro contiene el nombre del tipo que queremos usar para nuestra fbrica concreta. Es importante destacar que en este caso, si el tipo no implementa IWidgetFactory se producir una excepcin (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; } } }

Cdigo 15 - En este caso, hemos hecho ms flexible el ejemplo presentado en el cdigo 14 permitiendo que se configure el tipo de la fbrica concreta que queremos utilizar. Por lo tanto, podemos cambiar de fbrica concreta sin modificar el cdigo del Singleton que expone la nica instancia. En el fragmento a continuacin se muestra un ejemplo del fichero de configuracin donde se establece el parmetro que determina el tipo de fbrica a crear.

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

Cdigo 16 - Ejemplo de fragmento del fichero de configuracin donde se especifica el tipo de la fbrica concreta. Este valor se compone del nombre del tipo y el nombre del ensamblado separados por una coma. Principio de la pgina

4. Ejemplo de Aplicacin: Un cach de parmetros


En esta seccin mostraremos un escenario de implementacin de este patrn. Para esto, construiremos una cach de parmetros de configuracin. El funcionamiento de la cach es el siguiente: 1. 2. El cliente de la cach solicita el valor de un parmetro La cach busca el valor solicitado en su tabla de datos. a. Si lo encuentra, lo retorna al cliente. b. Si no lo encuentra: c. Busca la informacin solicitada en el fichero de configuracin (utilizando System.Configuration). d. Si la encuentra, la aade a su tabla de datos para que en las peticiones sucesivas no tenga que volver a buscarla. e. Retorna el valor al cliente.

La cach esta implementada como un Singleton. Los clientes acceden a la instancia a travs de la propiedad Instance. Esta propiedad est implementada utilizando Double-Check Locking (explicado en la seccin 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 aadir 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(); } }

Cdigo 17 - Cdigo fuente de la cach. Notar que los accesos a los recursos crticos se realizan utilizando bloqueos. A continuacin, se muestra un fichero de configuracin 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>

Cdigo 18 - Ejemplo de fichero de configuracin. El siguiente fragmento muestra un ejemplo de uso de la cach (cdigo 17) utilizando el fichero de configuracin que hemos presentado arriba (cdigo 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);

Cdigo 19 - Ejemplo de utilizacin de la cach. Notar cmo al solicitar un parmetro que no existe en el fichero de configuracin se obtiene null. Adicionalmente, si solicitamos dos veces el mismo parmetro obtenemos el mismo objeto. Esta cach puede ser modificada para recuperar parmetros de una tabla de una base de datos o de cualquier otro repositorio. Como la versin actual utiliza tambin el patrn Template Method, podemos crear muy fcilmente una cach que lea parmetros de una base de datos (o cualquier otro repositorio) heredando esta clase y redefiniendo el mtodo GetDataFromConfigRepository (que hace las veces de Operacin Primitiva en el patrn Template Method.

En caso de redefinir la clase, debemos volver a crear los elementos (campos y funciones) para gestin de la instancia nica. En el ejemplo de cdigo a continuacin mostramos como crear una cach que lea parmetros de configuracin a partir del ejemplo anterior.

public class ConfigurationDataCacheFromDB: ConfigurationDataCache { protected static new ConfigurationDataCacheFromDB instance = null; /// <summary> /// Obtener la instancia unica (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 mtodo 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; } }

Cdigo 19 - Ejemplo de redefinicin de la cach para leer la informacin desde una base de datos. Notar que el cuerpo de la clase slo tiene el mtodo que hemos redefinido y los elementos para la gestin de la instancia nica.

Existen otras formas de redefinir un Singleton. Podramos optar por el camino que hemos utilizado en el ejemplo de combinacin con los patrones de fabricacin. 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 tambin optar por la utilizacin del patrn Strategy (que ser motivo de estudio en una artculo posterior) para poder variar dinmicamente el algoritmo de recuperacin de datos del repositorio de informacin. Principio de la pgina

5. Referencias
[Box02] Box, Don: Essential .NET, Volume 1: The Common Language Runtime, Addison Wesley, 2002 [DCL] [DOTNET02] [DPE01] Bacon David et al: The "Double-Checked Locking is Broken" Declaration DOTNET Archives: The DOTNET Memory Model, 2002, 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] [Fowler99] Freeman, Eric et al: Head First Design Patterns, OReilly, 2004 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] [Hejlsberg03] [Kerievsky04] [Lowy02] [MSDN02] Gunnerson, Erich: A Programmer's Introduction to C#, APress, 2002 Hejlsberg, Anders et al: The C# Programming Language, Addison-Wesley, 2003 Kerievsky, Joshua: Refactoring to Patterns, Addison-Wesley, 2004 Lowy, Juval: Programming .NET Components, OReilly, 2002 Microsoft Patterns: Implementing Singleton in C#, 2002

[Msft05] [MsftMonitor] [PPR04] [Singleton] [SingletonEvil] Microsoft Coporation: Monitor Class C2 WikiWikiWeb: Portland Pattern Repository Implementing the Singleton Pattern in C# C2 Wiki: Singletons are Evil, 2005

[SingletonGood] C2 Wiki: Singletons are Good, 2005 [Srinivasan01] [Townsend02] [Vlissides98] [Welicki05] [Welicki05b] Srinivasan, Paddy: An Introduction to Microsoft .NET Remoting Framework, 2001 Townsend, Mark: Exploring the Singleton Design Pattern, 2002 Vlissides, John: Pattern Hatching: Design Patterns Applied, Addison Wesley, 1998 Welicki, Len: Patrones y Antipatrones: una introduccin, Revista MTJ .Net, 2005 Welicki, Len: Patrones de Fabricacin, Revista MTJ .Net, 2005

Len Welicki es Profesor Asociado de Ingeniera Web en el Mster en Ingeniera del Software de la Universidad Pontificia de Salamanca, Madrid, Espaa; donde actualmente est realizando el Doctorado en Ingeniera Informtica, su tesis doctoral trata sobre las Arquitecturas de Software y Paradigmas No Convencionales para Ingeniera Web. Trabaja como Arquitecto de Software. Cuenta con ms de 12 aos de experiencia profesional en diversas reas de la Ingeniera del Software. Principio de la pgina

Você também pode gostar