Un Patrón Singleton Genérico

Descripción general

Aunque parece fácil escribir una clase genérica que envuelva la funcionalidad del Patrón singleton, no es tan fácil porque se presentan una serie de problemas que si bien no son importantes (si es uno mismo el que usa la clase y tiene cuidado), la realidad es que no quedan ‘redondas’ por lo que en este articulo se estudia y desarrolla una técnica que permite resolver esos problemas y poder usar la clase genérica con el patrón Singleton de una forma natural

[TOC] Tabla de Contenidos


↑↑↑

Un Patrón Singleton Genérico


↑↑↑

A modo de introducción

En este documento no se trata nada de los problemas de concurrencia que existen al usar el patrón. Ese es un problema (que como dice Michael Ende en su Historia Interminable) será contado en otra ocasión. En este documento solamente se estudia el problema de la construcción de una clase base genérica que implemente el patrón Singleton


↑↑↑

Primera aproximación

A poco que se haya trabajado en programación sabemos que el patrón Singleton es un patrón de comportamiento básico, que se utiliza frecuentemente, lo que implica repetir en varios objetos el código necesario para usar el patrón.

Una de las reglas básicas en programación es no repetir código. Por esta razón, he creado una clase base que usando genéricos permita usar el patrón Singleton simplemente heredando la clase base.

La primera restricción de la clase son los calificadores de la clase base

Public MustInherit Class SingletonBaseGenerica(Of T As {Class})

El Modelo básico de la clase base genérica Singleton será el siguiente:

Public MustInherit Class SingletonBaseGenerica(Of T As {Class})

    Private Shared _SingletonObjetoInterno As T

    Public Function GetInstance() As T
            If (_SingletonObjetoInterno Is Nothing) Then
                _SingletonObjetoInterno = New T
            End If
            Return _SingletonObjetoInterno
        End Get
    End Function
End Class

Aunque no lo parece, al empezar a usarla aparece un problema en la propiedad [GetInstance], y consiste en la función necesita acceder al constructor de la clase hija. (observa: _SingletonObjetoInterno = New T ) Para ello, ese constructor (necesariamente) tiene que ser público.

Esto significa que el código anterior tiene un problema importante, por dos razones, la primera es que un objeto Singleton no debe tener un constructor de acceso público, y la segunda es que la clase hija, puede instanciarse usando ese constructor y entonces este ejercicio no sirve para nada

Podríamos pensar que la solución es fácil. Hacemos el constructor de la clase Hija [Private] y asunto resuelto ¿no?... pues no, lo que ocurre entonces es que en la línea

_SingletonObjetoInterno = New T

Se genera un error porque no se puede localizar el constructor de la clase (T)


↑↑↑

La solución

Resumiendo… Tenemos que encontrar una forma de que la clase Base Genérica que estamos creando pueda usar un constructor privado de la clase Hija y al solución está en usar reflexión

El código que usaremos será el siguiente:

Public Function GetInstance() As T
    '-------------------
    If (_SingletonObjetoInterno Is Nothing) Then
        Try
            '-----------------------------------------------------
            ' Utilizar reflexión para localizar el 
            ' constructor de la clase (Of T)
            Dim constructor As Reflection.ConstructorInfo = Nothing
            '-----------------------------------------------------
            ' Utilizar Binding flags para excluir constructores públicos.
            ' BindingFlags (Enumeración) Especifica los marcadores que 
            '    controlan el enlace y la manera en que se realiza 
            '   la búsqueda de miembros y tipos por reflexión. 
            ' Instance  Indica que se deben incluir los miembros 
            '           de instancia en la búsqueda.  
            ' NonPublic  Indica que se deben incluir los miembros 
            '            no públicos en la búsqueda.  
            constructor = GetType(T).GetConstructor( _
                (Reflection.BindingFlags.Instance Or _
                 Reflection.BindingFlags.NonPublic), _
                Nothing, _
                Type.EmptyTypes, _
                Nothing)
            '-----------------------------------------------------
            ' * si el constructor es Nothing o bien
            ' * el constructor es visible como mucho para otros tipos del
            '   mismo ensamblado y no es visible para los tipos derivados 
            '   fuera del ensamblado. es decir es private o protected,
            '-----------------------------------------------------
            If ((constructor Is Nothing) OrElse _
                 constructor.IsAssembly) Then
                ' componer el mensaje de error
                Dim mensajeError As String
                mensajeError = String.Format( _
                    "No se encuentra un constructor 'private' o " & _
                    " 'Protected'  para el objeto '{0}'.", _
                      GetType(T).Name)
                ' Escribir en el LOG
                My.Application.Log.WriteEntry( _
                     mensajeError, TraceEventType.Error)
                ' disparar exception
                Throw New Exception(mensajeError)
            End If
            '-----------------------------------------------------
            ' si no existe (todavia) la instancia de esta clase 
            ' crearla usando el constructor (private o Protected) 
            ' obtenido por reflexion
            If _SingletonObjetoInterno Is Nothing Then
              _SingletonObjetoInterno = CType(constructor.Invoke(Nothing), T)
            End If
        Catch exception As Exception
            Throw
        End Try
    End If ' (_SingletonObjetoInterno Is Nothing) 
    '-----------------------------------------------------
    ' Devolver el valor de la instancia
    Return _SingletonObjetoInterno
End Function

De esta forma resolvemos el problema... por una parte tenemos nuestra clase base que implementa la lógica del patrón singleton, y por otra obligamos a que las cases que vayan a heredar de esta clase base no tengan ningún constructor publico, lo que permite cumplir con la restricción básica del patrón: las clases Singleton no deben tener un constructor de acceso público. Además… si no se cumple esa restricción, se dispara un error en tiempo de ejecución lo que impide cualquier error involuntario.


↑↑↑

Ejemplo de código

En el fichero adjunto encontraras una clase genérica para el Patrón Singleton que resuelve el problema de los entornos multihilo

Código MD5 para los paranoicos


↑↑↑

A.2.Enlaces

[Para saber mas]
[Grupo de documentos]
[Documento Index]
[Documento Start]
[Imprimir el Documento]
© 1997 - - La Güeb de Joaquín
Joaquín Medina Serrano
Ésta página es española

Codificación
Fecha de creación
Última actualización