Hola, en esta entrada del blog intentaré explicar de manera fácil y sencilla que son los delegados y mencionaré algunos casos en que se pueden usar y combinar con Generics para mejorar la reusabilidad de nuestro código.
Para facilitar el entendimiento de estos conceptos partiremos de la implementación de un algoritmo simple y común que cualquier programador debería conocer. Éste es el algoritmo de ordenamiento de Burbuja (Bubble Sort en inglés):
- Public Class BubbleSorter
- Public Sub Sort(ByVal Lista As IList(Of Persona))
- Dim HuboCambio As Boolean
- Do
- HuboCambio = False
- For i As Integer = 0 To Lista.Count - 1
- If Compare(Lista(i), Lista(i + 1)) > 0 Then
- Dim oTemp As Persona = Lista(i)
- Lista(i) = Lista(i + 1)
- Lista(i + 1) = oTemp
- HuboCambio = True
- End If
- Next
- Loop While (HuboCambio)
- End Sub
- Private Function Compare(ByVal a As Persona, ByVal b As Persona) As Integer
- Return a.Nombre.CompareTo(b.Nombre)
- End Function
- End Class
- Public Class Persona
- Public Property Nombre As String
- Public Property ApellidoPaterno As String
- Public Property ApellidoMaterno As String
- End Class
Este algoritmo es muy sencillo. Simplemente recorre una lista de objetos de tipo Persona y compara la propiedad Nombre de los objetos que están consecutivos y los intercambia si es necesario para que queden ordenados alfabéticamente de menor a mayor.
Esto está bien pero ¿qué pasa si queremos ordenar la lista de objetos en base a otra propiedad? o ¿qué pasa si los objetos que queremos ordenar son de otro tipo? Sería magnífico si el método Sort de nuestra clase BubbleSort fuera más reusable y más genérico sin necesidad de cambios ¿verdad?
Bueno, pues para eso, podemos extender la funcionalidad de nuestro método de ordenamiento implementando la interface IComparable(Of T) en la clases que la usen, por ejemplo, veamos como quedaría dicha implementación en la clase Persona:
- Public Class Persona
- Implements IComparable(Of Persona)
- Public Property Nombre As String
- Public Property ApellidoPaterno As String
- Public Property ApellidoMaterno As String
- Public Function CompareTo(ByVal otro As Persona) As Integer _
- Implements IComparable(Of Persona).CompareTo
- Return Nombre.CompareTo(otro.Nombre)
- End Function
- End Class
Y el método Sort requeriría un par de pequeños cambios:
- Public Sub Sort(Of T As IComparable(Of T))(ByVal Lista As IList(Of T))
- Dim HuboCambio As Boolean
- Do
- HuboCambio = False
- For i As Integer = 0 To Lista.Count - 1
- If Lista(i).CompareTo(Lista(i + 1)) > 0 Then
- Dim oTemp As T = Lista(i)
- Lista(i) = Lista(i + 1)
- Lista(i + 1) = oTemp
- HuboCambio = True
- End If
- Next
- Loop While (HuboCambio)
- End Sub
Ahora, nuestro método Sort es genérico y acepta una lista de objetos de cualquier tipo que implemente la interface IComparable(Of T). Así el método de ordenamiento del ejemplo no solo puede ser usado con la clase Persona, sino con cualquiera con la única condición de que implemente la interface ya mencionada.
Ahora bien, si tenemos una clase que no podemos modificar porque solo tenemos una librería (DLL) y no su código, de tal forma que no podemos implementar la interface IComparable. Bueno, sería genial si pudiéramos definir el método Compare fuera de la clase BubbleSort o de las clases en la librería de tal forma que este método haga las comparaciones necesarias y se pueda pasar directamente un puntero a dicho método que creamos, hacia el método Sort de nuestra clase BubbleSort.
Bueno, pues eso es posible gracias a una estructura de programación llamada Delegado. Ésta nos permite invocar uno o varios métodos a la vez y se puede ver como un objeto que “apunta” a métodos sin importar si son métodos compartidos de una clase o métodos de una instancia y que se puede usar como punteros seguros a funciones.
Para que se entienda mejor lo que dice el párrafo anterior. Se puede decir que cuando se crea un delegado, éste “guarda” la dirección de memoria (como una referencia) de una función que se pasa como argumento al constructor de dicho delegado. Por ejemplo, cuando se crea o usa un evento, internamente se está usando un objeto delegado, así que cuando este evento es lanzado, el framework examina el delegado relacionado con ese evento y usa la función a la que apunta el delegado.
Ahora bien, volviendo al ejemplo, supongamos que ahora queremos ordenar la lista por la combinación de los apellidos de los objetos Persona. Supongamos también que la clase Persona no es modificable, porque solo tenemos acceso a ella porque es expuesta por una librería de clases (DLL). El método comparador podría quedar de la siguiente manera en un módulo u otra clase en nuestro proyecto:
- Private Function ComparePersonByApellidos(ByVal a As Persona, ByVal b As Persona) As Integer
- Return String.Format("{0} {1}", a.ApellidoPaterno, a.ApellidoMaterno).CompareTo(String.Format("{0} {1}", b.ApellidoPaterno, b.ApellidoMaterno))
- End Function
Y para poder utilizarlo, podríamos declarar un Delegado compatible con la declaración de este método:
- Public Delegate Function ComparerFunction(Of persona)(ByVal a As Persona, ByVal b As Persona) As Integer
O mejor aún, declarar este delegado como genérico y que sea más reusable y útil:
- Public Delegate Function ComparerFunction(Of T)(ByVal a As T, ByVal b As T) As Integer
Y para terminar, debemos cambiar el método Sort para que use el delegado y permanezca genérica.
- Public Sub Sort(Of T)(ByVal Lista As IList(Of T), ByVal Comparador As ComparerFunction(Of T))
- Dim HuboCambio As Boolean
- Do
- HuboCambio = False
- For i As Integer = 0 To Lista.Count - 1
- If Comparador(Lista(i), Lista(i + 1)) > 0 Then
- Dim oTemp As T = Lista(i)
- Lista(i) = Lista(i + 1)
- Lista(i + 1) = oTemp
- HuboCambio = True
- End If
- Next
- Loop While (HuboCambio)
- End Sub
Con estos cambios al método Sort, éste queda como genérico y perfecto para utilizar un delegado que realice las comparaciones que mejor se ajusten a nuestras necesidades aunque no tengamos acceso al código de las clases cuyas instancias se están ordenando.
Para usar este código se puede tomar el siguiente ejemplo:
- Imports System.Text
- Imports DelegatesAndLambdas
- <TestClass()>
- Public Class BubbleSorterTest
- <TestMethod()>
- Public Sub CreateAndSortAList()
- Dim lista As List(Of Persona) = New List(Of Persona)
- lista.Add(New Persona() With {.Nombre = "Noé", .ApellidoPaterno = "García", .ApellidoMaterno = "Urías"})
- lista.Add(New Persona() With {.Nombre = "Enrique", .ApellidoPaterno = "Chávez", .ApellidoMaterno = "Castillo"})
- lista.Add(New Persona() With {.Nombre = "José", .ApellidoPaterno = "Escobedo", .ApellidoMaterno = "López"})
- lista.Add(New Persona() With {.Nombre = "Julio César", .ApellidoPaterno = "Salazar", .ApellidoMaterno = "Lindoro"})
- lista.Add(New Persona() With {.Nombre = "Arturo", .ApellidoPaterno = "Escobedo", .ApellidoMaterno = "López"})
- Dim callback As ComparerFunction(Of Persona) = New ComparerFunction(Of Persona)(AddressOf CompareByApellidos)
- Call New BubbleSorter().Sort(lista, callback)
- Assert.AreEqual("García", lista(3).ApellidoPaterno)
- Assert.AreEqual("Lindoro", lista(4).ApellidoMaterno)
- Assert.AreEqual("Enrique", lista(0).Nombre)
- End Sub
- Public Function CompareByApellidos(ByVal a As Persona, ByVal b As Persona) As Integer
- Return String.Format("{0} {1}", a.ApellidoPaterno, a.ApellidoMaterno).CompareTo(String.Format("{0} {1}", b.ApellidoPaterno, b.ApellidoMaterno))
- End Function
- End Class
En este método de prueba, se puede ver como se agregan 5 objetos de tipo Persona a una Lista, luego se crea una instancia del delegado para realizar las comparaciones, posteriormente se ejecuta la ordenación y al final se evalúa el ordenamiento y se confirman los resultados.
Este tema da para más e incluso, puedo decir que el código donde se manejan delegados se puede reducir aún más con el uso de Métodos Anónimos, pero eso solo se puede hacer con C# ya que nunca ha estado disponible para VB y creo que nunca lo estará gracias a la aparición de las fabulosas Expresiones Lambda, que son una forma corta y concisa de escribir delegados, pero hablaré más sobre éstas en otra entrada del blog.
Espero haber sido lo suficientemente claro y explícito para que este tema se entendiera ya que, cuando hable posteriormente sobre las Expresiones Lambda los ejemplos estarán relacionados con los que aquí se han mostrado y porque cuando hable de como usar los distintos tipos de delegados y como usar todo esto con Cooperator Framework, un buen entendimiento de estas estructuras de programación será necesario.