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.
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 "vuelveme a llamar") 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):
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:
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:
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.
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 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.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).