Delegate
Bước đầu tiên để làm quen với phương thức ẩn danh
Bước đầu tiên để làm quen với phương thức ẩn danh
Chúng ta thường sử dụng con trỏ để truy cập đến các biến, và thông qua con trỏ để có thể truy xuất được giá trị biến được trỏ tới. Ta nói con trỏ làm đại diện để truy cập biến một cách gián tiếp.
Tương tự như vậy, delegate được sử dụng để truy cập gián tiếp đến các hàm, thủ tục, phương thức. Về khái niệm delegate tương tự như khái niệm function pointer (con trỏ hàm), về cơ bản thì như một con trỏ trỏ đến các hàm, thủ tục, phương thức.
Delegate đại diện cho hàm nên nó phải có cấu trúc giống như hàm, nghĩa là các tham số (và giá trị trả về) phải giống nhau. Delegate và hàm có cấu trúc giống nhau thì sẽ gán được cho nhau (cùng kiểu). Thêm điều nữa, delegate không được chứa tên của hàm.
Trong delphi có 3 dạng delegate: function pointer, method pointer và anonymous function pointer.
Dạng function pointer
type
TSimpleProc = procedure;
TSimpleFunc = function: Integer;
TIntFunc = function (x: Integer): Integer;
var
Proc1: TSimpleProc;
Func1: TSimpleFunc;
Func2: TIntFunc;
Dạng method pointer
type
TStringProc = procedure (s: string) of object;
TFunc = function: string of object;
var
Proc3: TStringProc;
Func4: TIntFunc;
Dạng anonymous function pointer
type
TProc = reference to procedure (s: string);
TFunc = reference to function: Integer;
var
Proc5: TProc;
Func6: TFunc;
Như trên là phần khai báo cho từng loại delegate
Sau đây là phần giải thích sự khác biệt giữa 3 loại trên:
Method pointer: Như tên gọi của nó, nó chỉ có thể trỏ đến một method (phương thức) của một object. Ứng dụng của method pointer là xử lý các event của control, như click vào nút chẳng hạn. Phần event này sẽ được bàn sau.
Function pointer: Loại delegate này chỉ có thể trỏ đến các hàm thông thường (function, procedure), không thể sử dụng với class.
Anonymous function pointer: Loại này kết hợp sức mạnh của 2 loại delegate trước đó, vừa có thể trỏ đến hàm thông thường, vừa trỏ đến được method của class. Bên cạnh đó, bạn có thể gán trực tiếp một phương thức ẩn danh vào nó mà không cần khai báo trước. Việc này sẽ bàn ở bài sau.
Như trong các ví dụ khai báo trên, bạn có thể thấy các delegate được khai báo sau từ khóa type. Điều đó cho thấy delegate có thể xem như một kiểu dữ liệu. Còn các biến kiểu delegate như Func1, Proc1, Func5, ... là các delegate object (đối tượng delegate). Việc sử dụng delegate cần thông qua các đối tượng delegate này.
Function pointer được sử dụng để thay thế cho một hàm, thủ tục thông thường. Ví dụ, ta có một thủ tục ShowName để in ra tên một người như sau
procedure ShowName(Name: string);
begin
writeln(Name);
end;
Tiếp đó, ta khai báo delegate có cấu trúc giống như thủ tục trên, nhưng bỏ qua phần tên hàm.
type
TNameProc = procedure (Name: string);
var
aProc: TNameProc;
Trong thân chương trình, thay vì sử dụng trực tiếp như sau
begin
ShowName('Tong Hoang Vu');
end;
Bạn có thể sử dụng function pointer để sử dụng hàm một cách gián tiếp.
begin
aProc := ShowName;
aProc('Tong Hoang Vu');
end;
Hai đoạn code trên là tương đương nhau. Nói chung, phần function pointer này mình cũng ít khi sử dụng, nhưng học thêm cho biết thôi.
Như trên, mình sẽ chỉ ví dụ tóm tắt phần này. Method pointer dùng để trỏ đến một method của object (không phải của class), cách sử dụng tương tự như function pointer như trên.
type
TTwoIntFunc = function (x, y: Integer): Integer of object;
TCalculator = class
function Sum(a, b: Integer): Integer;
end;
function TCalculator.Sum(a, b: Integer): Integer;
begin
Result := a + b;
end;
var
TinhToan: TTwoIntFunc;
Calc: TCalculator;
begin
Calc := TCalculator.Create;
TinhToan := Calc.Sum;
// Trỏ TinhToan đến method Sum của object Calc (object, không phải class TCalculator)
writeln(TinhToan(10, 5)); // In ra 15
Calc.Free;
end;
Khi lập trình giao diện với delphi (bằng VCL hoặc FMX), thì sự kiện là rất quan trọng. Sự kiện ví dụ như click vào nút, double click vào nút, nhập nội dung cho edit, nhấn phím, ... tất cả đều là sự kiện (event). Mỗi control (nút, edit, label, ...) đều là những class, đều có những event.
Như vậy, event thực chất là những method pointer, thường là thuộc kiểu TNotifyEvent.
type
TNotifyEvent = procedure (Sender: TObject) of object;
TButton = class
...
function FOnClick: TNotifyEvent;
property OnClick: TNotifyEvent read FOnClick write FOnClick;
...
end;
Lợi ích của điều này là gì. À thì ví dụ, khi bạn click vào 1 button có sẵn, sẽ tự động tạo ra 100 nút, với mỗi nút như thế bạn muốn khi click vào sẽ hiển thị 1 thông báo "Clicked". Bạn sẽ làm như thế nào. Bạn không thể kéo thả từng nút vào form được vì có quá nhiều nút. Bạn cần một cách khác.
Đó gọi là kĩ thuật tạo động các control. Theo cách này, bạn cần một event handle (không biết dịch sao luôn)
Đây là sự kiện click vào nút đầu tiên để tạo ra 100 nút tiếp theo
procedure TForm1.Button1Click(Sender: TObject);
var
Button: array [1..100] of TButton;
i: Integer;
begin
for i := 1 to 100 do
begin
Button[i] := TButton.Create(Form1);
Button[i].Parent := Form1;
Button[i].Top := 10 * i;
end;
end;
Điều này sẽ tạo ra 100 button mới khi click vào button1. Tiếp theo, để xử lý sự kiện click cho 100 button, chúng ta cần sử dụng method pointer. Dễ thấy sự kiện OnClick có cấu trúc giống nhau, nên chúng ta có thể sử dụng chung 1 method pointer. Và khi đó gọi nó là event handle.
Tạo event handle OnClick. Bởi vì method pointer cần một class để chứa, nên ta phải đặt event handle trong một class. Cho dễ thì đặt luôn vào class TForm1 luôn.
type
TForm1 = class(TForm)
procedure Button1Click(Sender: TObject); // Sự kiện OnClick cho nút đầu tiên
procedure handleOnClick(Sender: TObject); // Sự kiện OnClick dùng chung cho 100 nút
end;
procedure TForm1.handleOnClick(Sender: TObject);
begin
ShowMessage('Clicked');
end;
Trong đoạn code tạo 100 button ở trên, với mỗi đối tượng button thì gán handleOnClick cho thuộc tính OnClick là xong.
procedure TForm1.Button1Click(Sender: TObject);
var
Button: array [1..100] of TButton;
i: Integer;
begin
for i := 1 to 100 do
begin
Button[i] := TButton.Create(Form1);
Button[i].Parent := Form1;
Button[i].Top := 10 * i;
Button[1].OnClick := handleOnClick;
end;
end;
Chạy thử và cho mình biết kết quả nhé !