DMarlon Studio

Lección 14 · 14 de 20

Tipos procedurales y callbacks · Pascal

Pase funciones y métodos como parámetros en Object Pascal con tipos procedurales y of object, el mecanismo de los eventos de Lazarus.

Pascal · Tipos procedurales y callbacks en DMarlon Studio: el código del ejemplo y su salida en la terminal

Un tipo procedural describe la forma de una función: TPriceRule = function(price: Double): Double. Cualquier función con esa firma encaja, y Sale (un 20 % de descuento) y Clearance (un 50 %) la cumplen. En modo objfpc, `@Sale` obtiene la referencia a la función para pasarla como un valor más.

Checkout recibe la regla de precio y la llama como si fuera una función normal: rule(price). Así la misma rutina de cobro sirve para cualquier descuento presente o futuro sin tocar su código; es lo que se llama un callback.

`of object` cambia el juego: TSaleEvent no apunta a una función suelta, sino a un método de un objeto concreto. Por dentro guarda dos cosas, el código y el objeto (Self). Por eso @till.ShowSale lleva consigo a till y el método puede escribir su Name, Till 1.

Antes de llamar a un evento conviene comprobar `Assigned(onSale)`: si nadie se ha suscrito vale nil y llamarlo provocaría un fallo. Así funcionan los eventos de Lazarus: OnClick es un procedure(Sender: TObject) of object, y en las aplicaciones de escritorio de DMarlon Studio cada botón engancha un método del formulario exactamente igual.

Código · main.pas

program main;type  TPriceRule = function(price: Double): Double;          // any function of this shape  TSaleEvent = procedure(price, total: Double) of object;  // a method + its object  TTill = class    Name: String;    procedure ShowSale(price, total: Double);  end;procedure TTill.ShowSale(price, total: Double);begin  WriteLn('[', Name, '] ', price:0:2, ' -> ', total:0:2);end;function Sale(price: Double): Double;begin  Result := price * 0.80;end;function Clearance(price: Double): Double;begin  Result := price * 0.50;end;procedure Checkout(price: Double; rule: TPriceRule; onSale: TSaleEvent);begin  if Assigned(onSale) then onSale(price, rule(price));  // only if someone listensend;var till: TTill;begin  till := TTill.Create;  till.Name := 'Till 1';  Checkout(59.90, @Sale, @till.ShowSale);  Checkout(59.90, @Clearance, @till.ShowSale);  till.Free;end.

Salida · fpc main.pas && ./main

[Till 1] 59.90 -> 47.92
[Till 1] 59.90 -> 29.95

Para recordar

  • Un tipo procedural es la firma de una función.
  • @Funcion obtiene la referencia en modo objfpc.
  • of object guarda un método y el objeto al que pertenece.
  • Compruebe Assigned antes de llamar a un evento.
  • Los eventos de Lazarus (OnClick…) son métodos of object.

Ejercicio

Escriba una regla Member que reste 5 EUR a precios de más de 30, cree un segundo TTill llamado 'Till 2' y cobre con él 59.90 usando @Member.