viernes, 8 de marzo de 2013

Buddy classes en .NET

Las Buddy classes son una técnica para añadir metadatos a clases que son generadas automáticamente por herramientas, ya sean estas herramientas del Visual Studio o herramientas externas; también son útiles para la segregación de responsabilidades.

La introducción de clases parciales, que permiten ampliar los miembros de una clase creada por una herramienta sin que volver a crear la clase se machaque nuestra personalización, es una gran idea, pero tiene un problema a la hora de modificar un miembro ya declarado. Si en vez de añadir nuevos miembros a una clase queremos añadir un atributo a un miembro ya existente nos encontramos con un problema, puesto que el atributo añadido a un miembro de la clase se borraría al regenerar la clase de nuevo.

Para solventar este problema tenemos las buddy clases. Estas son clases que amplían los metadados de otra clase, pudiendo definir el miembro de la clase a modificar dentro de la buddy y añadiéndole el atributo que corresponda.

Imaginemos que tenemos la siguiente clase generada por una plantilla T4 en VS:

Partial Public Class Clasificacion

    Private _ID as String

    Private Descripcion as String



    Public Sub New(ByVal id As String)

    End Function



    Public Property ID() As String

    End Property



    Public Property Descripcion() As String

    End Property

End Class

Queremos añadir un atributo de validación a la propiedad Descripcion, por lo que debemos crear una Buddy Class con esa propiedad sin implementación y asociar esa Buddy Class a la extensión de la clase parcial utilizando el atributo MetadataType.

Public Class ClasificacionMetadata

<StringLength(50, ErrorMessage:="El campo descripcion de una Clasificacion no puede superar los 50 caracteres.")> _
    Public Property Descripcion() As String
        Get
        End Get
        Set
        End Set
    End Property
End Class

<MetadataType(GetType(ClasificacionMetadata))> _
Partial Public Class Clasificacion
    Public Function tieneDescricion As Boolean
        Return not String.IsNullOrEmpty(Me.Descripcion)
    End Function
End Class

Con esta definición, algunas tecnologías .NET recuperan el atributo MetadataType utilizando reflexión y obtendrán el atributo StringLengthAttribute de la Buddy Class cuando procesen la propiedad Descripcion de la clase generada.

Desgraciadamente, si nuestro código necesita procesar estos metadatos a través de la reflexión de tipos debemos programar explícitamente la posibilidad de que la clase parcial tenga asociado un MetadataType, recuperarlo y al procesar la clase parcial buscar en la clase Buddy su correspondencia para mirar si tiene algún atributo que extienda algún miembro. Esto es muy oscuro, aburrido y provoca que todo aquel que toque el codigo fuente de nuestro programa tenga conocimiento de la necesidad de este comportamiento. No me gusta.

Por suerte, .NET provee una manera de ampliar las descripciones de las clases administradas para que sean procesadas de forma transparente por el framework de reflexión de tipos.


Para esto tenemos en el framework unos proveedores de descripción de tipos. Simplemente debemos crear un nuevo proveedor y asociárselo al tipo Clasificacion.

Public Class BuddyClassesHelper

    Private Shared IsRegistered As Boolean = False

    Public Shared Sub RegisterBuddyClasses()

        If Not IsRegistered Then
            Dim descriptionProvider = New AssociatedMetadataTypeTypeDescriptionProvider(GetType(Clasificacion), GetType(ClasificacionMetadata))
            TypeDescriptor.AddProvider(descriptionProvider, GetType(Clasificacion))
            IsRegistered = True
        End If

    End Sub
End Class

Esto se realizará en la lógica de inicialización de nuestra aplicación. Una vez asociado el proveedor, podremos listar, utilizando la reflexión de tipos, los atributos de la propiedad Descripcion de la Clase Clasificacion y obtendremos el StringLengthAttribute de forma transparente.

Para mi gusto esta solución que provee Microsoft es un poco fea, puesto que se carga toda las recomendaciones DRY (Don't Repeat Yourself) ya que hay que volver a declarar en la Buddy class todos los miembros a los que queramos extender metadatos, y en caso de que la clase parcial cambie, debemos cambar a mano la clase buddy.

Aun así, es útil y si se maneja con cuidado puede ser una solución cómoda en algunos casos.

No hay comentarios:

Publicar un comentario