Delegados en Delphi. Pasando metodos y funciones como parametros.

Que es un delegado

Un delegado (de la palabra inglesa delegate) es un prototipo de función es decir, un esquema que debemos seguir al declarar una función. La palabra delegado proviene (o mejor dicho yo la escuche por primera vez) de C# y probablemente en delphi sería más conveniente referirse a ellas como tipos de funciones.

Los delegados se utilizan fundamentalmente para proporcionar tipado al paso de funciones como parametros a otras funciones o metodos.

Para que sirve un delegado

Si alguien ha programado alguna vez en C sabrá que pasar funciones como parametros esta a la orden del día, estas funciones suelen conocerse con el nombre de funciones de CallBack (algo así como "devuelveme la llamada") e implican que tu estás pasando una función como parametro que quieres que sea invocada cuando dicha función tenga unos ciertos datos preparados.

Una buena parte de las funciones de la API de windows funcionan de esta forma, por ejemplo si observamos la ayuda de la función EnumWindows de la API de windows (kernel32.dll):

La funcion EnumWindows enumera todas las ventans de alto nivel de la pantalla pasando el manejador de cada ventana a una funcion de callback definida por la aplicación. EnumWindows ejecuta hasta que la última ventana sea enumerada o hasta que la función de callback devuelva falso.

    BOOL EnumWindows(

    WNDENUMPROC lpEnumFunc,     // pointer to callback function
    LPARAM lParam       // application-defined value
   );

vemos que dicha función recibe dos parametros, uno de ellos siendo un puntero a una función de callback, es decir, una función que será invocada una vez por cada ventana que encuentre la función.

Los eventos de Delphi (es decir, cosas como el OnClick de un botón o el OnChange de un combo box) son punteros a funciones que serán invocadas cuando dicho evento se produzca. Todos los eventos son de algún tipo predefinido, por ejemplo el evento OnClick es del tipo TNotifyEvent cuya definición es la siguiente:

  type TNotifyEvent = procedure TNotifyEvent(Sender : TObject) of Object;

Otro uso bastante común es en las librerías de funciones. En ocasiones programamos una determinada función que realiza una serie de pasos genéricos pero que, en alguno de sus pasos necesita ejecutar alguna función definida por el usuario.

Un ejemplo claro de esto último podemos verlo en la función Sort de las listas de Delphi. Delphi utiliza el algorítmo QuickSort (probablemente el mejor mecanismo de ordenado en cuanto a complejidad) para ordenar dichas listas. En un TStringList por ejemplo, por defecto delphi aplica dicho algorítmo para ordenar la lista alfabeticamente. Sin embargo puede darse el caso de que queramos ordenar la lista de una forma más arbitraria. Para ello Delphi nos provee del metodo CustomSort que acepta un parametro de tipo TStringListSortCompare:

type TStringListSortCompare = function(List: TStringList; Index1, Index2: Integer): Integer;

De esta forma la función acepta otra función como parametro, que, en este caso, se llamará cada vez que se necesite comparar dos items cualesquiera de la lista.

¿Que conseguimos con esto? El beneficio es inmediato, tenemos una sola definición del QuickSort, que es un algoritmo de ordenado genérico y mediante el CustomSort estamos permitiendo que el usuario nos indique como comparar los items, de forma que dicho usuario se beneficia del mecanismo de ordenación rápida sin tener que estar limitado a un particular forma de comparar los items.

Un caso práctico

Hace poco he tenido un caso en el que el uso de un delegado era fundamental. Yo estaba declarando un interface llamado IRellenable que define un tipo que puede rellenarse con valores aleatorios. El problema es que dicho tipo no tenía por que rellenarse solo, es decir, en algunos casos era necesario que quien estuviera interesado en rellenar el objeto proporcionara los medios para rellenarlo. Es decir, por poner un ejemplo algo más gráfico, podriamos decir que un vaso y un jarron son rellenables pero pueden rellenarse con distintas cosas (agua, vino, arena) y quien decide con que se rellenan es el que realiza el rellenado.

Puesto que el llamante (el que usa el objeto rellenable) debía poder llenar el objeto como el quisiera:

type IAgregable = interface(IInterface)
  procedure Add(item : Pointer);
end;

type TFuncionRellenado = procedure(list : IAgregable) of object;

type IRellenable = interface(IInterface)
  procedure Rellenar(FuncionRellenado : TFuncionRellenado);
end;

que luego podremos usar de la siguiente forma (por ejemplo)

procedure MiObjeto.FuncionRellenoBotones(list : IAgregable);
var
  i : integer;
begin
  for i := 0 to CONSTANTE_MAX_BOTONES do
    list.Add(TButton.Create(nil));
end;

procedure MiObjeto.FuncionRellenEdits(list : IAgregable);
var
  i : integer;
begin
  for i := 0 to CONSTANTE_MAX_EDITS do
    list.Add(TEdit.Create(nil));
end;

procedure MiObjeto.ProcesaParametros(obj1 : TInterfacedObject; obj2 : TInterfacedObject);
var
  iface : IRellenable;
begin
  iface := obj1 as IRellenable;
  if Assigned(Rellena) then
    iface.Rellenar(Self.FuncionRellenoBotones);

  iface := obj2 as IRellenable;
  if Assigned(obj2) then
    iface.Rellenar(Self.FuncionRellenoEdits);
end;

De esta forma mi funcion recibe dos objetos que pueden o no ser rellenables. Si lo son puedo rellenarlos con lo que yo quiera (en este caso con botones y edit box).

6
Average: 6 (2 votes)
Your rating: None