domingo, 19 de julio de 2015 0 comentarios

Delegados de manera fácil

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):

  1. Public Class BubbleSorter
  2.     Public Sub Sort(ByVal Lista As IList(Of Persona))
  3.         Dim HuboCambio As Boolean
  4.  
  5.         Do
  6.             HuboCambio = False
  7.  
  8.             For i As Integer = 0 To Lista.Count - 1
  9.                 If Compare(Lista(i), Lista(i + 1)) > 0 Then
  10.                     Dim oTemp As Persona = Lista(i)
  11.                     Lista(i) = Lista(i + 1)
  12.                     Lista(i + 1) = oTemp
  13.                     HuboCambio = True
  14.                 End If
  15.             Next
  16.  
  17.         Loop While (HuboCambio)
  18.  
  19.     End Sub
  20.  
  21.     Private Function Compare(ByVal a As Persona, ByVal b As Persona) As Integer
  22.         Return a.Nombre.CompareTo(b.Nombre)
  23.     End Function
  24.  
  25. End Class
  26.  
  27. Public Class Persona
  28.  
  29.     Public Property Nombre As String
  30.     Public Property ApellidoPaterno As String
  31.     Public Property ApellidoMaterno As String
  32.  
  33. 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:

  1. Public Class Persona
  2.     Implements IComparable(Of Persona)
  3.  
  4.     Public Property Nombre As String
  5.     Public Property ApellidoPaterno As String
  6.     Public Property ApellidoMaterno As String
  7.  
  8.     Public Function CompareTo(ByVal otro As Persona) As Integer _
  9.         Implements IComparable(Of Persona).CompareTo
  10.  
  11.         Return Nombre.CompareTo(otro.Nombre)
  12.     End Function
  13. End Class 

Y el método Sort requeriría un par de pequeños cambios:

  1. Public Sub Sort(Of T As IComparable(Of T))(ByVal Lista As IList(Of T))
  2.     Dim HuboCambio As Boolean
  3.  
  4.     Do
  5.         HuboCambio = False
  6.  
  7.         For i As Integer = 0 To Lista.Count - 1
  8.             If Lista(i).CompareTo(Lista(i + 1)) > 0 Then
  9.                 Dim oTemp As T = Lista(i)
  10.                 Lista(i) = Lista(i + 1)
  11.                 Lista(i + 1) = oTemp
  12.                 HuboCambio = True
  13.             End If
  14.         Next
  15.  
  16.     Loop While (HuboCambio)
  17.  
  18. 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:

  1. Private Function ComparePersonByApellidos(ByVal a As Persona, ByVal b As Persona) As Integer
  2.    Return String.Format("{0} {1}", a.ApellidoPaterno, a.ApellidoMaterno).CompareTo(String.Format("{0} {1}", b.ApellidoPaterno, b.ApellidoMaterno))
  3. End Function

Y para poder utilizarlo, podríamos declarar un Delegado compatible con la declaración de este método:

  1. 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:

  1. 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.

  1. Public Sub Sort(Of T)(ByVal Lista As IList(Of T), ByVal Comparador As ComparerFunction(Of T))
  2.     Dim HuboCambio As Boolean
  3.  
  4.     Do
  5.         HuboCambio = False
  6.  
  7.         For i As Integer = 0 To Lista.Count - 1
  8.             If Comparador(Lista(i), Lista(i + 1)) > 0 Then
  9.                 Dim oTemp As T = Lista(i)
  10.                 Lista(i) = Lista(i + 1)
  11.                 Lista(i + 1) = oTemp
  12.                 HuboCambio = True
  13.             End If
  14.         Next
  15.  
  16.     Loop While (HuboCambio)
  17.  
  18. 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:

  1. Imports System.Text
  2. Imports DelegatesAndLambdas
  3.  
  4. <TestClass()>
  5. Public Class BubbleSorterTest
  6.  
  7.     <TestMethod()>
  8.     Public Sub CreateAndSortAList()
  9.         Dim lista As List(Of Persona) = New List(Of Persona)
  10.  
  11.         lista.Add(New Persona() With {.Nombre = "Noé", .ApellidoPaterno = "García", .ApellidoMaterno = "Urías"})
  12.         lista.Add(New Persona() With {.Nombre = "Enrique", .ApellidoPaterno = "Chávez", .ApellidoMaterno = "Castillo"})
  13.         lista.Add(New Persona() With {.Nombre = "José", .ApellidoPaterno = "Escobedo", .ApellidoMaterno = "López"})
  14.         lista.Add(New Persona() With {.Nombre = "Julio César", .ApellidoPaterno = "Salazar", .ApellidoMaterno = "Lindoro"})
  15.         lista.Add(New Persona() With {.Nombre = "Arturo", .ApellidoPaterno = "Escobedo", .ApellidoMaterno = "López"})
  16.  
  17.         Dim callback As ComparerFunction(Of Persona) = New ComparerFunction(Of Persona)(AddressOf CompareByApellidos)
  18.  
  19.         Call New BubbleSorter().Sort(lista, callback)
  20.  
  21.         Assert.AreEqual("García", lista(3).ApellidoPaterno)
  22.         Assert.AreEqual("Lindoro", lista(4).ApellidoMaterno)
  23.         Assert.AreEqual("Enrique", lista(0).Nombre)
  24.  
  25.     End Sub
  26.  
  27.     Public Function CompareByApellidos(ByVal a As Persona, ByVal b As Persona) As Integer
  28.         Return String.Format("{0} {1}", a.ApellidoPaterno, a.ApellidoMaterno).CompareTo(String.Format("{0} {1}", b.ApellidoPaterno, b.ApellidoMaterno))
  29.     End Function
  30.  
  31. 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.

 
;