Refactory - Refactoreo del Software - Parte I

Introducción y Fundamentos
El desarrollo de los algoritmos que permiten la construcción del software, normalmente se construyen a través de alguna técnica de diseño, basándose en planteos orientado generalmente a los objetivos que justifican su desarrollo. Dado que el primer paso es construir el algoritmo exitosamente, en los primeros movimientos, es problable que no se piense en optimizar los algoritmos. Quizá le suene poco profesional, pero siempre en un principio cuando se desarrolla un algoritmo, puede que se recurra a la imaginación e improvisación. Desgraciadamente, muchos desarrolladores se detienen en este punto. Lo normal sería diseñar los algoritmos, luego someterlos a testing, métricas de ingeniería para determinar la mantenibilidad, costes, etc. Como último paso, el refinamiento para mejorar su performance y también su legibilidad, sin olvidar su debida documentación final. El Refactory proporciona muchas ventajas. Por empezar, la optimización y refinamiento del código y, por el otro lado, la documentación de forma rápida, apropiada y libre de ambigüedades. Deseo hacer una puntualización importante. Las técnicas de Refactoreo pueden aplicarse a cualquier tipo de lenguaje de programación. Solo ha de conocerse bien su sintaxis según el lenguaje en el que trabaje y sus mecanismos de desarrollo más apropiados. De esta forma, a través de Refactory, podrá mejorar sus códigos. Técnicas de Refactoreo Un Refactoreo Básico solo se trata de optimizar la legibilidad del código y en algunas oportunidades mejorar substancialmente el rendimiento del mismo. Muchos desarrolladores creen que escribir todo en una sola línea es una técnica válida de Refactoreo. La respuesta es No. Por ejemplo: Button2.BackColor = Color.Violet : Button2.ForeColor = Color.White : ... Este trozo de código no encierra estrategias de Refactoreo, de hecho, lo invitaría a que evite este tipo de codificaciones por varias razones. La primera y la de mayor peso es la organización de su código y por otro lado evitar potenciales errores de diversa índole. Como siempre le digo a mis alumnos, "Que Ud., tenga un Automóvil y lo utilice para llevar un mueble de su cocina a la casa de su tía, no significa que su Automóvil se trate de una Pick-Up o 4x4". Aquí ocurre exactamente lo mismo. Que funcione no significa que sea apropiado. Refactoreo Sencillo Para ver un caso de refactoreo muy sencillo, analicemos el siguiente código que está hecho en Visual Basic .NET Insisto nuevamente, esta técnica podría aplicarla en otros lenguajes. Tan solo deberá encontrar la sintaxis apropiada para cada estructura de refactoreo. Como verá, no resulta para nada complicado. Button2.BackColor = Color.Violet Button2.ForeColor = Color.White Button2.Text = "Mi Botón" Button2.TextAlign = ContentAlignment.MiddleCenter Button2.Width = 80 Button2.Height = 30

Si observa detenidamente vera que el objeto Button2 se repite en cada línea para cada propiedad asignada. Pues bien, una sencilla forma de Refactorizar este código, simplemente, bastará con organizar el código utilizando el estamento With...End. Véase el siguiente resultado de un Refactoreo extremadamente sencillo. With Button2 .BackColor = Color.Violet .ForeColor = Color.White .Text = "Mi Botón" .TextAlign = ContentAlignment.MiddleCenter .Width = 80 .Height = 30 End With Ahora bien, siguiendo de cerca el Refactoreo recientemente hecho, aún podemos seguir mejorando nuestro código. Si observa las propiedades finales Width y Heigth, como sabrá, ambas responden a la clase Size. Bien, por tanto, podemos utilizar la clase Size para pasarle mediante una instancia constructor los valores de Width y Height en una sóla línea. Aquí sí no solo ahorramos líneas de código y legibilidad, sino que también, mejoramos el rendimiento del código dado que lo estamos orientando fuertemente a objetos. Veamos como quedaría esta nueva modificación. With .BackColor .ForeColor .Text .TextAlign .Size = End With Refactoreo de Distribución - Divide y Vencerás Como la famosa cita "Divide y Vencerás" del genial Fiorentino Niccolò di Bernardo dei Machiavelli, o más conocido por Maquiavelo, en el mundo de la programación esta técnica resulta extremadamente útil. En las técnicas de Refactoreo, también resultan apropiadas. La división o la técnica de atomización para ser más precisos, permite separar grandes trozos de código en pequeñas porciones más o menos manipulables. Esta facilidad permite a los desarrolladores hacer más legible el código. Además de ello, permite operar estrategicamente en las áreas de interés. Cuando se realiza mantención o corrección del código, el desarrollador se enfoca en el área apropiada haciendo foco en dicho sector y realizando una suerte de abstracción del resto del conjunto de códigos que componen el gran proceso. Si en lugar de contar con áreas separadas se tiene lo contrario, es decir, todo en una sola gran área, se corren ciertos riesgos. Es más, resulta más marañada la tarea y la legibilidad del código. Esto se puede evitar y la mejor técnica es la de atomizar los grandes procesos en procesos más pequeños. Este es el aspecto de la interfáz gráfica y debajo muestro el código para su control. Button2 = Color.Violet = Color.White "Mi Botón" ContentAlignment.MiddleCenter Point(80, 30)

= = New

A continuación el código para la gestión de la Calculadora de Plazo Fijo. Public Class Form1 Private Sub controlButtons(ByVal sender As System.Object, System.EventArgs) Handles Button1.Click, Dim inversion As Decimal Dim tna As Decimal Dim plazo As Integer Dim residual As Decimal Dim capitalFinal As Decimal If sender.Equals(Button1) inversion = tna = plazo = residual = (inversion * plazo * (tna / 12)) / capitalFinal = inversion + TextBox4.Text = TextBox5.Text = Catch ex As MsgBox("Valor numérico no TextBox1.Text = TextBox2.Text = TextBox3.Text = TextBox4.Text = TextBox5.Text = End End If sender.Equals(Button2) TextBox1.Text = TextBox2.Text = TextBox3.Text = TextBox4.Text = TextBox5.Text = End End Sub End Class

ByVal

As _ Button2.Click = 0.0 = 0 = 0 = 0.0 = 0.0 Then Try TextBox1.Text TextBox2.Text TextBox3.Text (365 * 100) residual residual capitalFinal Exception válido") 0 8.5 30 0 0 Try If Then 0 8.5 30 0 0 If

e

Si bien. me permito volcar directamente el Refactoreo de este código e ir explicando cada uno de los cambios realizados de modo que queda más clara esta estrategia.Text TextBox3.Text TextBox5.Object.Text TextBox2.5 30 0 0 Sub ByVal As _ Button2.Equals(Button2) End End End Class Empezaremos analizando las variables.Text TextBox4. El código quedaría de la siguiente forma.EventArgs) Handles Button1.Text / (365 * 100) + residual residual capitalFinal Sub = = = = = controlLimpiarValores() 0 8. el uso en este caso de las variables solo se realizan dentro del evento único clic de ambos botones. System.Text TextBox3.0 0 0 0.0 0.Para evitar recargar la lectura de este Blog.Click. Una razón es la de "separar" parte del código que originalmente estaba dentro de dicho evento y . Public Private Private Private Private Private inversion tna plazo residual capitalFinal Class As As As As As Sub = = = * (tna / inversion = = Decimal Decimal Integer Decimal Decimal = = = = = Form1 0.Text TextBox2.Click Then Try Exception válido") Try If Then If Sub e Private Sub controlButtons(ByVal sender As System.Text End Private TextBox1.Equals(Button1) controlCalcular() Catch MsgBox("Valor controlLimpiarValores() ex numérico As no If controlLimpiarValores() End End sender.Text TextBox5.0 Private inversion tna plazo residual = (inversion * plazo capitalFinal = TextBox4. If sender.Text End Sub 12)) controlCalcular() TextBox1. es mejor declararlas por fuera.

puede poner en práctica otras técnicas y. Esta situación foco y abstracción. resultaría algo compleja de entender dentro del gran proceso en el código original.0 Private Shared Function controlFormula(ByVal setInversion As Decimal. deseo que observe esta última actualización.0 Class As As As As Decimal Decimal Integer Decimal = = = = Form1 0. ¿qué ocurriría si Ud. le ha permitido al código original ahorrarse de unas cuantas líneas de código.0 0 0 0. Aquí también podemos facilitar lamantenibilidad. El procedimiento que realiza el cálculo financiero llamado controlCalcular(). ha facilitado la mantenibilidad. Public Private inversion Private tna Private plazo Private residual Private capitalFinal As Decimal = 0. Para ello. Ud. se encarga de limpiar y resetear a los valores iniciales dicha calculadora. quizá lo más interesante ocurre con los dos procedimientos que se han agregado en esta última actualización. ha sido extraido y ensamblado en otros dos procesos aparte.. encontraremos que al procedimiento que realiza el cálculo financiero llamadocontrolCalcular() podemos optimizarlo aún más. parte del código que estaba originalmente en el primer código. por lo tanto. las variables al declararse fuera.. Además. En cambio.evitar recargarlo demasiado. sino que a su vez. exhausativa e innecesaria. volviendo al código original. Si observa de cerca el procedimiento. He aquí una de las ventajas de reutilizanción de código que evita la "duplicación" de código que es otro punto negativo de la programación. lo invito a pensar en ello. La duplicación resulta en excesiva. Además. Aquí vuelvo a volcar todo el código nuevamente con la actualización. Esta estrategia de distribuir las variables no es la única y ni la absoluta. fácil de comprender y extrictamente dinámica. Esto podría tener como resultado final severas consecuencias. Siguiendo el lineamiento de "atomización" y por otro lado "optimización". notará que cualquier cambio que desee realizar en esta sección. el otro procedimiento. Refinando Aún Más Nuestro Código Analizando en detalles de forma más profunda. al agrupar en un proceso todas estas líneas. El procedimiento encargado de limpiar y resetear la calculadora llamado controlLimpiarValores(). debía actualizar estos valores en cada una de las líneas escritas relacionadas.. Un proceso ejerce el cálculo para la calculadora de plazo fijo o financiera y. recargada. _ ByVal setPlazo As Integer. podrían ser reutilizadas por otros procedimientos o funciones. _ . lo cual y sin duda alguna. podemos refinar aún más nuestro código. Seguro que encontrará más de una solución para este caso. Esto es a lo que me refiero hacer foco a un proceso y abstraerse del resto del código. bastará entonces operar directamente dentro de dicho proceso. Deseo hacer una puntualización. debería actualizar algún dato? Por ejemplo. resulta en más legible. será más trabajo y con mayores riesgos a comenter errores u omisiones. Bien continuando con nuestro acometido. Si tiene que hacer algún cambio o actualización. Como notará. Aquí planteo una de las tantas posibilidades. no solamente que ha ahorrado código y líneas escritas. que la taza TNA sea 7% y que el Plazo sea de 12 días. Ud.

. If sender. capitalFinal = inversion TextBox4.Text plazo. Por empezar.Text TextBox2. para que luego.Equals(Button2) End End End Class La última modificación he Refactoreado el procedimiento controlCalcular(). he creado una nueva función para controlar el cálculo agrupando una línea de dicha sección en una función homogenea la que he llamado con el nombre de controlFormula(parámetros.Text TextBox3.Equals(Button1) controlCalcular() Catch MsgBox("Valor controlLimpiarValores() ex numérico As no If controlLimpiarValores() End End sender.Object. Voy a justificarlo para que lo entienda mejor.Text TextBox5.Click. Para ello extraje la sección donde figura la fórmula que permite hallar la inversión del capital en función al tiempo y el interés nominal anual TNA.Return End Sub (setInversion * ByVal setTNA As setPlazo * (setTNA / Decimal) As 12)) / (365 Decimal * 100) Private Sub inversion = tna = plazo = residual = controlFormula(inversion. La función simplemente se encarga de realizar el cálculo y transferir el valor resultado a una variable.)..EventArgs) Handles Button1. como decía.Text End Sub controlCalcular() TextBox1.Text TextBox4. las funciones resultan . En la República de Argentina. tna) + residual residual capitalFinal Sub controlLimpiarValores() = 0 = 8. sea calculada como resultado final.Text = TextBox5. Para cálculos de interés.Text TextBox3.. System. véase reglas de Intereses (Wikipedia). Volviendo a nuestro acometido.5 = 30 = 0 = 0 Sub ByVal As _ Button2. esté pensando ¿cuál es el beneficio de hacer esto? La respuesta es "rendimiento". es regida por el Banco Central de la República de Argentina BCRA.Text = End Private TextBox1. Estos parámetros pueden variar entre países. Es probable que Ud.Click Then Try Exception válido") Try If Then If Sub e Private Sub controlButtons(ByVal sender As System.Text TextBox2.

No hace falta pensar demasiado que esta función estaría sometida a un nivel de exigencia bastante importante. Tabla de Ingeniería para el Modelo Original Mantenibilidad Ciclo de Complejidad Profundidad de Herencia Acoplamiento de Clases Líneas de Código 67 45 7 28 185 Tabla de Ingeniería del Último Refactoreo Mantenibilidad Ciclo de Complejidad Profundidad de Herencia Acoplamiento de Clases Líneas de Código 69 48 7 28 183 Como podrá apreciar en las tablas. Imagine ahora que esta función de cálculo es utilizada en un sitio dinámico Web. En efecto. imagínese una librería la cual es utilizada por muchos sistemas a la vez. las funciones son adecuadas para estas situaciones. las funciones soportan perfectamente estas exigencias y son adecuadas para procesos donde los cálculos y las recursiones son frecuentes en sistemas que demandan dichos procesos de forma masiva. debido a que hay que tener conocimientos sólidos de Hebras o Hilos. Otro concepto importante de la separación del código y. Ciclo de Complejidad y la cantidad de Líneas de Código de toda la aplicación. Es más. se pueda acceder a ella a través de clases y. más precisamente. pero al menos. es probable que algunos valores no muestren grandes rasgos característicos de rendimiento. Pese a que es un desarrollo muy pequeño y sencillo. Front-End. aquí tiene otra posibilidad más para aplicar Refactory para su mejoramiento y optimización de su código. Mejor aún.ser más seguras y rápidas. No obstante. es la posibilidad de utilizar Thread "Hilos o Hebras" para facilitar la programación paralela. aunque es un tema muy complejo para analizar aquí en este apartado. Explico brevemente cada uno de ellos para entender el resultado y cerrar esta primera parte del Blog. hasta en una Intranet Corporativa. la cual. Por ejemplo. . donde existe un sistema de contabilidad que requiere el uso de sus servicios. nos darán una idea de los avances que se producen entre estos y las consecuencias que tendrían en la práctica sin duda alguna. de esta forma. Métricas Para Este Ejemplo A continuación muestro una serie de tablas que muestran las métricas de este ejemplo. reutilizarla en las soluciones diversas Desktop. si esta función es construida y compilada en una librería aparte. Uno de los procesos que exigen de una performance adecuada son las recursiones. Esto mejoría la performance aún más. En consecuencia. también estaría en parte sometida a peticiones de diversos clientes en dicha red. Han sido creadas para realizar diversos procesos exhaustivos que los procedimientos de por si no soportan. Servicios para el Servidor. existe una marcada diferencia entre el factor de Mantenibilidad. en el uso de funciones separadas para procesos más rápidos o ágiles. etc.

por otro lado.El valor de Mantenibilidad. no lo es desde el punto de vista de costes y performance. Recuérdese que es un parámetro al que se tiende lograr y. Muchas Gracias por su Atención. Para nuestra aplicación. podría resultar crítico e interesante para otros análisis. no hablaré de ello. es un índice que atañe a la complejidad analítica y no al grado de mantención. El ahorro de líneas no siempre puede lograrse en un ciento por ciento. este último. Aquí no hay motivo para analizar el Deeping "profundidad" y. es probable que los cite en la Parte II de Refactory porque si son muy importantes. por tanto. La diferencia podría ser más que importante. de no hacerlo no implica que el desarrollo sea crítico. en consecuencia. Esta lectura podría preocuparlo. valores menores a este la mantenibilidad resulta crítica. Por último. Parte II. El Ciclo de Complejidad hace referencia al diseño y distribución de los algoritmos. en especial. pero sencillamente. Eso si. Continuará en otra sección. esto nos demuestra que la última actualización ha resultado mejor para su mantención. hemos subido de 67 a 69. Esto que parece un logro pobre. En nuestro caso. Por tanto. tenemos el número de líneas que en esta oportunidad he logrado un ahorro de dos líneas para todo el proyecto. pero solo piense en un gran proyecto. indica un valor interesante y poco influente. Por empezar. Lo mismo es para Acomplamiento que son temas que escapan de este apartado. Job Systems Solutions . Aquí en esta pequeña aplicación. por tanto. pero en realidad es sencillo de entender. Otro dato que quiero señalar. según Microsoft. la complejidad puede incidir en materia de pruebas dado que resulta en más compleja o requiere de más análisis. No es este el caso. Sin embargo. escaparían de este tema que he planteado en este Blog. contamos con baja mantenibilidad. como citaré más adeleante en otros apartados. La Profundidad de Herencia atañe al volumen de descendencia de las clases intervinientes en un proyecto o aplicación. Sin embargo. Por ende. no podemos notar grandes diferencias como señale. debe ser lo más alto posible y oscila entre el número 20 y 100. El factor de acoplamiento es clave en el diseño de Software e Ingeniería.

5.2 Breve Discusión 2. consultar el libro del GoF [GoF95]. El patrón Singleton 2. 2.5 Ejemplos de Código 2.1 Single Responsibility Principle 3. haciendo hincapié en su correcto funcionamiento en entornos multihilos.2. Para una versión completa. .1 Definición del patrón 2.1.3 Ejemplo "No Software" 2.5.2 Significado de la palabra Singleton 2.3 Haciendo más flexible el ejemplo anterior con Reflection 4. Presentaremos distintas opciones de implementación en C#.1 Fábrica Concreta como Singleton 3.1.2 Exponiendo una Fábrica Concreta mediante un Singleton 3.1. exponiendo sus fortalezas y debilidades. Ejemplo de Aplicación: Un caché de parámetros 5.1.1 Ejemplo sencillo: implementación del GoF 2. Es también uno de los patrones más conocidos y utilizados.El Patrón Singleton Personas que lo han encontrado útil: 11 de 14 .2.5.1 Double-Check Locking y Java 2.NET Framework 2. mostraremos un ejemplo de implementación de este patrón para crear una caché de parámetros de configuración. Singleton y los Patrones de Fabricación 3.3 Double-Check Locking 2.4 Ejemplos en .4 Utilización de Facilidades del . En este artículo analizaremos en profundidad este patrón. 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]).5 Singleton sin la función/propiedad "Instancia" 3. 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. Luego relacionaremos los conceptos de este artículo con los patrones de fabricación que hemos estudiado en la entrega anterior. Referencias 1.5.1 Singletonitis 2.Definición del patrón A continuación presentaremos una versión reducida de la plantilla de este patrón.5.3.1 La sentencia lock 2. Principio de la página 2. Finalmente. Introducción 2.Valorar este tema Por León Welicki Contenido 1.5.5.5.NET Framework 2.2 Primer mejora: thread safety 2. Su propósito es asegurar que sólo exista una instancia de una clase.1 Modificación del Ejemplo del GoF usando Propiedades 2.2 Problemas en ambientes multihilo 2.2.5.

2. Resumen 1 . In clase (static en C# y shared en VB . los resultados no son los mismos. Consecuencias  Acceso controlado a la única instancia. quitando así este problema a los clientes.NET). Ocultar el constructor de la clase Singleton. Figura 1: Diagrama OMT de Singleton. éstos no pueden ser extendidos. El funcionamiento de este patrón es muy sencillo y podría reducirse a los siguientes conceptos: 1. Puede tener un control estricto sobre cómo y cuando accede  Espacio de nombres reducido. Solución Garantizar una única instancia. El patrón hace que sea fácil cambiar de opinión y permit clase Singleton. para que los clientes no puedan crear instancias. ya que en este caso la responsabilidad de tener una única instancia recae en el cliente de la clase. Se puede crear una subclase de Singleto  Permite un número variable de instancias. uno puede pensar que pueden utilizarse clases con miembros estáticos para el mismo fin.  Permite el refinamiento de operaciones y la representación. El patrón Singleton hace que la clase sea responsable de su única instancia. Participantes  Singleton o Define una operación Instancia que permite que los clientes accedan a su única instancia. A primera vista. Aplicabilidad Usar cuando:  Deba haber exactamente una instancia de una clase y ésta deba ser accesible a los clientes desde un  La única instancia debería ser extensible mediante herencia y los clientes deberían ser capaces de u sin modificar su código.Breve Discusión El patrón Singleton asegura que exista una única instancia de una clase. Problema Varios clientes distintos precisan referenciar a un mismo elemento y queremos asegurarnos de que no hay elemento.NET). si todos los métodos de esta clase son estáticos. desaprovechando así las capacidades polimórficas que nos proveen los entornos orientados a objetos. .2. tomado de [GoF] y [DPE]. Declarar en la clase Singleton una variable miembro privada que contenga la referencia a la instancia única que queremos gestionar. Shared en VB . 2. o Puede ser responsable de crear su única instancia.Intención Garantiza que una clase sólo tenga una instancia y proporciona un punto de acceso global a ella. tomado del libro del GoF.  Más flexible que las operaciones de clase (static en C#.Vista simplificada y resumida del patrón Singleton. Adicionalmente. El patrón Singleton es una mejora sobre las variables globales. Sin embargo.

2. Los tipos de objeto son Single Call. bajo el sugerente título de "To Kill a Singleton". El ciclo de vida de los Singleton es un aspecto importante a tener en cuenta. Volver al texto.NET. se incluyen los significados (en inglés) encontrados para este término en el diccionario: 1.Singletonitis En Refactoring to Patterns [Kerievsky04] se presenta el término Singletonitis. Tthe playing card that is the only card in a suit held in a bridge hand as initially dealt. Para clarificar aun más la raíz etimológica de este patrón. Estas reglas se cumplen en todas las implementaciones del Singleton. Proveer en la clase Singleton una función o propiedad que brinde acceso a la única instancia gestionada por el Singleton. En la Figura 2 se muestra un diagrama UML del ejemplo comentado arriba. As a result. Un ejemplo de implementación de este patrón puede encontrarse en Remoting. los Singleton son objetos que sirven a múltiples clientes y comparten datos almacenando información de estado entre las distintas invocaciones. limits the term of office. 2. The United States Constitution specifies the means by which a president is elected.1. there can be at most one active president at any given time. 3. Pero como todo. El tipo de objeto puede ser elegido en función de los requisitos de la aplicación. independientemente de los recaudos que deban tomarse para soportar la correcta ejecución en entornos multihilo.2. Set containing a single member.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".Ejemplos en . 2. refiriéndose a "la adicción al patrón Singleton". En Patterns Hatching [Vlissides98] John Vlissides se plantea y estudia en profundidad el problema de "quién y cómo mata a un Singleton?". and defines the order of succession. Regardless of the personal identity of the active president.NET Framework El Singleton se utiliza en forma extensiva en . . 2. "The President of the United States" is a global point of access that identifies the person in the office. 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. 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. Singleton y Client-Activated Objects (CAO). tomado de [Duell97]. Los clientes acceden a la instancia a través de esta función o propiedad.3. En este contexto. 2. cuyo objetivo es remover los Singletons innecesarios en una aplicación. A single object (as distinguished from a pair).Ejemplo "No Software" En [Duell97] se presenta el siguiente ejemplo: The office of the President of the United States is a Singleton. debe utilizarse en su justa medida y en el contexto adecuado.NET. ¿Esto quiere decir que los Singletons son malos y no hay que usarlos? Definitivamente no. Figura 2: Ejemplo del mundo real del patrón Singleton.2.4.3. Este término aparece en la motivación del refactoring "Inline Singleton". the title. Existen tres tipos de objetos que pueden configurarse para servir como objetos remotos de .

En este ejemplo hemos utilizado una función para dar acceso a la instancia del Singleton. protected Singleton() {} public static Singleton Instance { get { if (instance == null) instance = new Singleton(). return instance.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.Modificación del Ejemplo del GoF usando Propiedades A continuación.NET (VB .NET. aunque son aplicables y pueden ser traducidos a cualquier otro lenguaje soportado por .5.Ejemplos de Código En esta sección presentaremos varias opciones de implementación de este patrón en . pero también puede utilizarse una propiedad (como se muestra en la sección 2.5. Para escribir los ejemplos utilizaremos C#.2.NET: las propiedades.). En este caso.1. hemos modificado el ejemplo original utilizando una propiedad en lugar de una clase.5. public class Singleton { private static Singleton instance = null. J#. En el bloque de código a continuación se muestra una traducción literal del ejemplo del GoF a C#. aprovechando las características de . private Singleton() {} public static Singleton GetInstance() { if (instance == null) instance = new Singleton(). . etc.NET.Ejemplo de implementación del Singleton usando propiedades.1. utilizando una característica de . } } Código 1 . return instance. } } } Código 2 . 2.NET.5. Es importante aclarar que esta implementación no funciona correctamente en entornos multihilo. mostraremos una versión del patrón Singleton modificada. public class Singleton { private static Singleton instance = null.1). 2.Traducción literal del ejemplo del GoF a C#. Es importante aclarar que esta implementación no funciona correctamente en entornos multihilo.1.

2. una instancia ya había incrementado su contador.. pero ésta todavía no ha sido creada. debido a cuestiones de sincronización y concurrencia. dado ese código de inicialización se ejecuta mas de una vez. se puede producir un memory leak.El uso de propiedades hace más cómoda la utilización del Singleton.GetInstance(). Por ejemplo. dos hilos solicitan la instancia a través de la función GetInstance. . En este caso. Si el Singleton es statefull (mantiene estado) se pueden producir errores sutiles. ese incremento se perderá. tiene serios problemas en entornos multihilo (multi-thread) dado que. se inicializará dos veces el contador. ¿Cómo puede ser esto posible? Imaginemos que dos hilos evalúan la condición instance == null y en ambos casos es verdadera. Por ejemplo.5. Si estamos en C++. pueden producirse inconsistencias. para acceder a una función de un Singleton llamada MiFunción se usaSingleton. en los dos casos se procede a la creación de la única instancia. En la Figura 3 intentaremos ilustrar una de las formas en que puede presentarse este problema. dado que sólo se va a eliminar uno de los objetos aunque hayamos creado dos. Los problemas que se producen a raíz de esto pueden ser muy difíciles de detectar. Si se producen dos creaciones. En este caso.Instance. pero debido a la ejecución de ese código de inicialización. ambos hilos crearán la instancia. usando como base el ejemplo presentado en el Código 2.MiFuncion() en lugar de Singleton.. o Tomemos como ejemplo de esto último un Singleton que implementa un contador. 2. Quizás en la segunda inicialización. Imaginemos que el constructor inicializa el contador a 0. [DPE01]    Si el Singleton es absolutamente stateless (es decir.1. si se modifica el estado del objeto en el constructor. 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. En contrapartida. Ahora bien. no mantiene ningún tipo de estado) puede no ser un problema. La creación dual suele producirse en forma intermitente e incluso puede no suceder (no es determinista). Por lo tanto.Problemas en ambientes multihilo Si estamos en un ambiente de un solo hilo (single-thread) esta implementación es suficiente. La columna de la derecha (Valor de Instance) muestra el valor de instance luego de que se ejecuta cada línea de código. puede crearse más de una instancia del miembro instance. ¿Es esto un problema? Puede o no serlo. 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.MiFuncion. violando el propósito del patrón Singleton.

En este caso. aun cuando la instancia ya ha sido creada. haciéndola segura para ambientes multihilo. return instance. mejoramos un poco la situación anterior.Ejemplo básico de implementación de Singleton para ambientes multihilo. . la utilización de recursos es ineficiente.5. Inspirada en [FF04]. private static readonly object padlock = new object().2.Primer mejora: thread safety En este ejemplo. public class Singleton { public sealed class Singleton { private static Singleton instance = null. dado que siempre se hace un lock sobre todo el código de la propiedad/función. Volver al texto. } } } } } Código 3 . private Singleton() {} public static Singleton Instance { get { lock(padlock) { if (instance == null) instance = new Singleton(). En la sección 2. 2.5.Figura 3: Representación gráfica de los problemas de sincronización en la implementación del Singleton de Código 1 y Código 2.3 se presenta una solución para este problema.

} Código 4 .3.. Los monitores soportan bloqueos exclusivos. La sentencia lock es un envoltorio delgado sobre las llamadas a Monitor.Sentencia lock Es traducido internamente por el compilador a . Esta cláusula inhibe opciones de reordenamiento y optimización del compilador. aunque acarrea graves problemas de rendimiento.Threading. que pueden producir . 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). El método Monitor.1.El ejemplo presentado arriba permite la ejecución segura en entornos multihilo. } Código 5 .Exit(object)suelta el bloqueo obtenido anteriormente para que esté disponible para otros clientes.5.Monitor.Threading. Esta versión tiene mejor rendimiento que la anterior.. Ejecuta un conjunto de sentencias. La sentencia lock realiza las siguientes tareas: 1. dado que el bloqueo sólo es necesario cuando se crea la instancia de instance. De esta forma. Finalmente.. El monitor permite asociar un bloqueo con cualquier objeto del sistema. 2. Try { System. notar la inclusión de la cláusula volatile en la declaración de la instancia interna del Singleton.2. Si los datos que se deben proteger son estáticos. 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. Obtiene un bloqueo exclusivo (mutual-exclusive lock) para un objeto.Double-Check Locking Double-check locking es un idioma ampliamente citado y eficiente para implementar inicialización tardía (lazy inicialization) en entornos multihilo. 2. } finally { System.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. // sentencias. Soporte para ambientes multihilo.. Luego suelta el bloqueo. se podría usar el ejemplar de referencia. será necesario bloquear usando un objeto de referencia estático y único.La sentencia lock La sentencia lock se utiliza para asegurar que un bloque de código se ejecuta hasta ser completado sin interrupciones.Enter() y Monitor. mientras que Monitor. 2.Monitor. 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).Exit(). Para esto.5.Exit(this). lo cual permite que sólo un hilo a la vez acceda al bloqueo.. al incurrir en menos bloqueos. 3. este código: lock(this) { // sentencias. Tiene las siguientes características:   Se evitan bloqueos innecesarios envolviendo la llamada al new con otro testeo condicional.Enter(object) adquiere un bloqueo para un objeto y lo bloquea.Enter(this). Por lo tanto. obtenemos un mejor rendimiento..

5.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).3. no utiliza la técnica de instanciación tardía (lazy instantiation) y por lo tanto crea la instancia del objeto Singleton inmediatamente. funciona en ambientes multihilo (multi-thread). private Singleton() {} } Código 7 . Una de las causas de este problema es la forma en que la máquina virtual de Java gestiona la memoria. } } } Código 6 . se recomienda ver el capítulo 29 de [Gunnerson02] y el 8 de [Lowy02].Ejemplo de Singleton implementado aprovechando características de . a pesar de su sencillez. se recomienda leer el artículo "The Double-Checked Locking is Broken Declaration" [DCL]. Esta versión. Para una explicación detallada de volatile. Para una explicación profunda sobre como gestiona el CLR los campos estáticos se recomienda leer el capítulo 3 de "Essential . } } return instance.Utilización de Facilidades del . En este caso se toma partido la forma en que el CLR gestiona los campos estáticos de las clases).NET Framework A continuación. Para una explicación profunda y detallada de este problema. public sealed class Singleton { private static volatile Singleton instance = null.NET Framework. private Singleton() {} public static Singleton Instance { get { if (instance == null) { lock(padlock) { if (instance == null) instance = new Singleton(). presentamos la versión más compacta y simple del patrón Singleton.NET" [Box02] . Este problema no se hace extensivo a J# (el compilador de Java para . A diferencia de las versiones anteriores.1.NET). public sealed class Singleton { public static readonly Singleton instance = new Singleton().Ejemplo de implementación del Singleton con Double-Check Locking en C# 2.resultados inesperados en entornos multihilo. 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.4.5. private static readonly object padlock = new object().

public sealed class Singleton { private int counter = 0. } } public int IncrementCounter() { return this. private static volatile Singleton instance = null. A continuación. En el ejemplo anterior.2. } } return instance. Código 9 . private static volatile Singleton instance = null.Ejemplo de uso del contador creado en el Código 8. En el bloque de código a continuación se muestra una clase que implementa un contador utilizando un Singleton.Singleton sin la función/propiedad "Instancia" En algunos casos. En estos casos. puede resultar incómodo exponer toda la funcionalidad de la clase a través del método GetInstance. private static readonly object padlock = new object(). private Singleton() {} . podemos llevar la gestión de la instancia a los métodos de la interface pública.5. para acceder a la instancia es necesario utilizar la propiedad Instance. el método para incrementar el contador es utilizado por los clientes en la siguiente forma: Singleton.5. El problema de este tipo de clases es que no pueden ser subclaseadas. } } Código 8 .Contador implementado con un Singleton. En este caso.IncrementCounter(). dado que su interface pública está basada en métodos estáticos.Instance. private Singleton() {} public static Singleton Instance { get { if (instance == null) { lock(padlock) { if (instance == null) instance = new Singleton(). rescribiremos el ejemplo presentado en el código 8 para que no utilice la propiedad instancia: public sealed class Singleton { private int counter = 0.counter++. private static readonly object padlock = new object().

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. Abstract Factory. Los resultados que se obtienen al ejecutar ambos ejemplos son los mismos. Existen formas más apropiadas de recubrir el método instancia. es buena idea marcar a la clase Singleton como sealed. Como regla general. ni se dan cuenta que están tratando con un Singleton).El mismo contador que implementamos en el código 8. Singleton y los Patrones de Fabricación En el artículo anterior. 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. pero no puede ser redefinido posteriormente. éste se encarga de la gestión de la instancia. Concretamente. ¿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 por ejemplo. lo cual indica que no puede ser redefinida. el uso conjunto de la composición y delegación de objetos. } } Código 10 . Código 11 .public static int IncrementCounter() { if (instance == null) { lock(padlock) { if (instance == null) instance = new Singleton(). El ejemplo de Singleton que se presenta arriba es utilizado por los clientes en la siguiente forma: Singleton. En el artículo anterior vimos como se relacionaban estos patrones. pero en este caso no tiene la propiedad Instance. En la Figura 3 se muestra la relación entre estos patrones. extendida y redefinida posteriormente. Al invocar el método estático. . dado que su interface se compone de un conjunto de métodos estáticos. 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).counter++. Patrones de Fabricación.  El segundo ejemplo es más cómodo para los usuarios (dado que no deben hacer referencia a la instancia. Principio de la página 3. } } return instance. a saber. Si optamos por la segunda opción. La relación es que "una fábrica concreta suele ser un Singleton" [GoF95]. presentamos varios patrones de fabricación.IncrementCounter(). Factory Method y Simple Factory. pero la clase Singleton puede ser heredada.Ejemplo de utilización del contador creado en el código 10. Notar que no es necesario invocar a la propiedad instancia. de hecho.

Figura 3: Relaciones existentes en el catálogo del GoF entre Singleton. } } . 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. El ejemplo de código a continuación muestra cómo implementar esta relación. 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. private SingletonWindowsWidgetFactory() {} public static SingletonWindowsWidgetFactory Instance { get { if (instance == null) { lock(padlock) { if (instance == null) instance = new SingletonWindowsWidgetFactory(). } } public Window CreateWindow() { return new WindowsWindow(). En este caso.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".1. private static readonly object padlock = new object(). Volver al texto. Abstract Factory y Factory Method. } public Scrollbar CreateScrollbar() { return new WindowsScrollbar(). } } return instance. 3.

El Singleton contiene una referencia a la fábrica concreta que se desea exponer. 3. De hecho.CreateScrollbar(). private static readonly object padlock = new object(). la mayoría de las implementaciones del patrón Singleton vulneran este principio. } } return internalFactory. Por lo tanto. Podemos tener un Singleton por cada Fábrica Concreta (tantos como decidamos implementar). Window theWindow = SingletonWindowsWidgetFactory. si tenemos más de una fábrica concreta disponible. Por lo tanto.1. Código 13 . exponemos una Fábrica Concreta a través de un Singleton.Instance.Exponiendo una Fábrica Concreta mediante un Singleton En el siguiente ejemplo de código.Invocación de los métodos de creación de la Fábrica Concreta utilizando el Singleton.Single Responsibility Principle El Single Responsibility Principle (en adelante SRP) establece que "una clase debe tener una única responsabilidad".2.1. 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. } } . que no se cumpla no es condición determinante de que un diseño sea malo o incorrecto. no estamos violando el SRP. dado que la clase SingletonWindowsWidgetFactory tiene la responsabilidad de implementar la Fábrica Concreta y el Singleton.Instance. De esta forma. private SingletonWidgetFactory() {} public static IWidgetFactory Instance { get { if (internalFactory == null) { lock(padlock) { if (internalFactory == null) internalFactory = new WindowsWidgetFactory().Código 12 . El ejemplo presentado en bloque de código 12 viola este principio. dado que la clase Singleton implementa una serie de funcionalidades además del aseguramiento de la existencia de una única instancia. 3. Se entiende por responsabilidad motivos por los que ésta pueda cambiar. public sealed class SingletonWidgetFactory { private static volatile IWidgetFactory internalFactory. podemos incurrir en el problema de consistencia presentado en el artículo anterior cuando estudiamos el patrón Simple Factory [Welicki05b].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.CreateWindow(). Es importante tener en cuenta que SRP es un principio abstracto de diseño y no una ley.

Combinación entre Singleton y Abstract Factory. 3. /// <summary> /// Constructor. En este caso. solo hay una instancia de una fábrica concreta para toda la aplicación.NET. al cual llamaremos ConcreteFactorType(el nombre ha sido elegido en forma totalmente arbitraria). En el ejemplo siguiente.config. } } . Para seleccionar el tipo de Fabrica Concreta usaremos un parámetro en el fichero de configuración App. Es importante destacar que en este caso. private static readonly object padlock = new object(). dado que la única responsabilidad de esta clase es asegurar que exista una única instancia de una fábrica concreta.GetType(typeName). Para que esta modificación se haga efectiva es necesario recompilar y volver a desplegar. Adicionalmente.} Código 14 .2).Configuration. using System.3. 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. public sealed class SingletonDynamicWidgetFactory { private static volatile IWidgetFactory internalFactory. La clase SingletonWidgetFactory retorna una instancia de la fábrica concreta adecuada para crear la familia de productos en el sistema. 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.Reflection.AppSettings["ConcreteFactoryT ype"]. estamos siguiendo el SRP. // creo el objeto dentro de este ensamblado Type t = Type. Este parámetro contiene el nombre del tipo que queremos usar para nuestra fábrica concreta. using System. nos asegurábamos la existencia de una única Fabrica Concreta de un tipo específico.CreateInstance(t). si el tipo no implementa IWidgetFactory se producirá una excepción (que no es controlada en el ejemplo). Esta situación puede ser evitada utilizando las capacidades reflectivas de .WindowsWidgetFactory y MacWidgetFactory). internalFactory = (IWidgetFactory)Activator. Para cambiar el tipo de la fábrica concreta. mostraremos cómo puede hacerse esto usando las facilidades deSystem.Haciendo más flexible el ejemplo anterior con Reflection En el ejemplo anterior (4.

Por lo tanto.En este caso. Ejemplo de Aplicación: Un caché de parámetros En esta sección mostraremos un escenario de implementación de este patrón. <appSettings> <add key="ConcreteFactoryType" value="SingletonDemo. la añade a su tabla de datos para que en las peticiones sucesivas no tenga que volver a buscarla. Implementa tambien Template Method /// </summary> public class ConfigurationDataCache { protected static ConfigurationDataCache instance = null.} // retorno la instancia de la factoria return internalFactory. 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. los accesos a la tabla interna de datos se serializan mediante bloqueos (usando la sentencia lock). El cliente de la caché solicita el valor de un parámetro 2.Configuration). /// Es un Singleton. /// <summary> /// Clase que aloja la informacion de configuracion. Los clientes acceden a la instancia a través de la propiedad Instance.WindowsWidgetFactory. private Hashtable data = null. esta caché puede ser utilizada en ambientes multihilo. Adicionalmente. /// <summary> /// Indizador. Si no lo encuentra: Busca la información solicitada en el fichero de configuración (utilizando System. 5. 4. La caché busca el valor solicitado en su tabla de datos. Recupera un valor del cache . Principio de la página 4.SingletonDemo"/> </appSettings> Código 16 . podemos cambiar de fábrica concreta sin modificar el código del Singleton que expone la única instancia. Para esto. El funcionamiento de la caché es el siguiente: 1. Si lo encuentra. 3. 1. 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. construiremos una caché de parámetros de configuración. Por lo tanto. Este valor se compone del nombre del tipo y el nombre del ensamblado separados por una coma. protected static readonly object padlock = new object(). La caché esta implementada como un Singleton.3). 2.5.Ejemplo de fragmento del fichero de configuración donde se especifica el tipo de la fábrica concreta. } } } Código 15 . Esta propiedad está implementada utilizando Double-Check Locking (explicado en la sección 2. Retorna el valor al cliente. Si la encuentra. lo retorna al cliente.

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.data. Inicializa la coleccion /// interna de datos /// </summary> protected ConfigurationDataCache() { data = new Hashtable(). } // y lo vuelvo a añadir instance. val).GetValue(key). /// Como esta marcado "virtual".data.Configuration.Add(key.data. } } /// <summary> .ConfigurationSettings. } } /// <summary> /// Constructor.Remove(key).. } /// <summary> /// Recupera un valor de un repositorio de configuracion../// de valores de configuracion /// </summary> public string this[string key] { get { return this.data.ContainsKey(key)) { // lo quito instance. 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. } /// <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.SyncRoot) { // si el elemento ya esta en la lista de datos. if (instance.AppSettings[key].

} else // si no esta en el cache. // si el dato esta en el cache. { // recupero el parametro del repositorio de valores de configuracion ret = this..data[key]. if (instance. ret).GetDataFromConfigRepository(key). // si lo ha encontrado..ContainsKey(key)) { // almaceno en la variable de retorno el valor del cache ret = instance.. lo almaceno en el cache if (ret != null) this. } } . } } } return instance. } /// <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().StoreDataInCache(key. } // retorno el valor del parametro solicitado return ret.data./// 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.ToString()..

no es necesario ir al fichero de // configuracion.Clear().Instance.data. // Obtiene el valor de param1.GetValue("param1").GetValue("ParamInexistente").Assert(object. En este caso.Assert(v3 == null).Instance.Instance.ReferenceEquals(v1.SyncRoot) { instance. Notar que los accesos a los recursos críticos se realizan utilizando bloqueos. En este caso. retorna null string v3 = ConfigurationDataCache. <?xml version="1. A continuación.Código fuente de la caché. } } } Código 17 . // Obtiene el valor de param1. // v1 y v2 deben ser referencias al mismo objeto Debug. // v3 debe ser nulo Debug.ContainsKey(key).Ejemplo de fichero de configuración. v2)). .data.GetValue("param1"). dado que ya lo tiene almacenado en el cache string v2 = ConfigurationDataCache. se muestra un fichero de configuración de ejemplo. 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).data. debe recuperarlo del fichero de configuracion string v1 = ConfigurationDataCache./// <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. Como no existe. // Busca el valor de ParamInexistente.0" encoding="utf-8" ?> <configuration> <appSettings> <add key="param1" value="Parametro 1"/> <add key="param2" value="Parametro 2"/> </appSettings> </configuration> Código 18 . } /// <summary> /// Limpia los datos de configuracion /// </summary> public void Clear() { lock(instance.

public class ConfigurationDataCacheFromDB: ConfigurationDataCache { protected static new ConfigurationDataCacheFromDB instance = null. 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. } } /// <summary> /// Recupera un valor de un repositorio de configuracion.. Como la versión actual utiliza también el patrón Template Method.. /// <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().Código 19 . . } } } return instance. 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. // . En caso de redefinir la clase. 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. /// Como esta marcado "override".Ejemplo de utilización de la caché. debemos volver a crear los elementos (campos y funciones) para gestión de la instancia única.. Notar cómo al solicitar un parámetro que no existe en el fichero de configuración se obtiene null. 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 // . Adicionalmente.

Podríamos optar por el camino que hemos utilizado en el ejemplo de combinación con los patrones de fabricación. 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 . podemos crear un registro de Singletons.return ret. Si queremos que varios tipos de Singleton convivan. 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. } } Código 19 . Existen otras formas de redefinir un Singleton.Ejemplo de redefinición de la caché para leer la información desde una base de datos. que será a su vez un Singleton encargado de gestionar las instancias de éstas clases (como se propone el libro del GoF).

Básicamente.lang. nos permite separar cierta lógica de soporte de la lógica propia de la aplicación. A pesar de que hoy el concepto es muy conocido se puede venir haciendo desde el JDK 1.reflect. sacar trazas. aplicar seguridad.reflect.Proxy. java. Supongamos que necesitamos medir cuanto tiempo tardan en ejecutarse los métodos de un determinado objeto (en realidad de una interfaz).TreeSet. java. sin embargo.util. En este caso nos tenemos que limitar a lo que nos proporciona el propio JDK. En vez de calcular el tiempo dentro de la propia clase que queremos medir podemos implementarlo mediante un aspecto con la clase Proxy de la siguiente forma (el método main no es realmente necesario solo está incluido para hacer simple el probar el ejemplo): import import import import import import import java. En vez de añadir el código de estas funcionalidades a las clases de nuesta aplicación lo podemos añadir a una clase que se encarge de implementar el aspecto.InvocationHandler. La librería Spring proporciona un amplio soporte para la programación orientada a aspectos. java.lang. java. java.reflect.. obtener conexiones a base de datos.Programación orientada a aspectos con la clase Proxy oxy La programación orientada aspectos (AOP) es uno de los conceptos que en estos días se usa muy habitualmente en el desarrollo de aplicaciones Java.Set. por ejemplo.util.util.HashSet.. hay veces en el que no tenemos control sobre las librerías que podemos utilizar en nuestro proyecto.3 (desde el año 2000) a través de la clase Proxy y su uso es realmente sencillo para las ventajas que aporta al código de una aplicación.Method. cuantas veces se llama. public class ProfileProxy implements InvocationHandler { .util. se puede utilizar si necesitamos medir el tiempo que tarda en ejecutarse un determinado método. etc.lang. java.Date. gestionar la transaccionalidad.

getProxy(). i < 100000. public ProfileProxy(Object object) { this. } public Object invoke(Object proxy. Object[] args) throws Throwable { long start = new Date(). return o.getInterfaces().addAll(data).start) + "ms").getTime(). Method method.addAll(data). long end = new Date().getClass(). Set proxyHashSet = (Set) new ProfileProxy(hashSet).getClassLoader(). } HashSet hashSet TreeSet treeSet = new HashSet(). protected Proxy proxy.println("Tiempo: " + (end . Set proxyTreeSet = (Set) new ProfileProxy(treeSet). // Invocar al servicio Object o = method. } public Proxy getProxy() { return proxy. this).getProxy(). } } . = new TreeSet(). object.add(new Integer((int) (Math.out.getClass().protected Object object. } public static void main(String[] args) throws Exception { Set data = new HashSet(). for (int i = 0. // Creación del proxy que intercepta las llamadas a los métodos del objeto proxy = (Proxy) roxy.getTime(). args).invoke(object.newProxyInstance(object. proxyHashSet. proxyTreeSet. ++i) { data.object = object. System.random() * 100000))).

. Dado que la clase TreeSet mantiene los elementos ordenados según su orden natural es de esperar que tarde más tiempo. una pequeña limitación de utilizar la clase Proxy es que la interceptación de las lamadas se tienen que hacer sobre los métodos de una interfaz que implemente el objeto. Pero todo no son cosas buenas.El método main del ejempo trata de medir cuanto tiempo se tarda en añadir los elementos de un Set a otro para un HashSet y para un TreeSet. En mi equipo añadir cien mil elementos de un Set a un HashSet tarda 62ms y añadir el mismo número de elementos a un TreeSet 110ms.