Khái niệm về Metatable và Metamethod
Trong aardio, Metatable (bảng siêu dữ liệu) là một kỹ thuật cho phép chúng ta thay đổi hành vi mặc định của các bảng (table) đối với các toán tử hoặc hàm hệ thống. Nếu bạn đã từng làm việc với Python, cơ chế này rất giống với "Magic Methods" (như __str__, __add__). Trong aardio, các phương thức này thường bắt đầu bằng dấu gạch dưới, ví dụ _tostring tương ứng với hành vi chuyển đổi đối tượng thành chuỗi.
Sử dụng Metamethod cơ bản
Mặc định, khi bạn in một table ra console, aardio sẽ hiển thị địa chỉ bộ nhớ của table đó. Để tùy chỉnh nội dung hiển thị, chúng ta có thể sử dụng metamethod _tostring.
import console;
var infoTable = {
name = "aardio";
version = "2024";
@{
_tostring = function() {
// Sử dụng table.tostring để chuyển đổi dữ liệu bên trong table thành chuỗi
// Lưu ý: owner đại diện cho chính table hiện tại
return table.tostring(owner);
}
}
}
console.log("Mặc định (không metatable):", { x = 1 });
console.log("Tùy chỉnh (có metatable):", infoTable);
console.pause();
Ghi đè toán tử (Operator Overloading)
Bạn có thể định nghĩa cách các table tương tác với nhau thông qua các toán tử như cộng (+), trừ (-), nhân (*), v.v. Ví dụ dưới đây minh họa cách cộng hai đối tượng tọa độ:
import console;
var point1 = { x = 10; y = 20 };
var point2 = { x = 5; y = 15 };
// Gán metatable để định nghĩa phép cộng
point1@ = {
_add = function(target) {
return {
x = owner.x + target.x;
y = owner.y + target.y;
};
}
};
var result = point1 + point2;
console.log("Kết quả phép cộng tọa độ:");
console.dump(result); // Hiển thị {x=15; y=35}
console.pause();
Kiểm soát truy cập thuộc tính với _get và _set
Hai metamethod _get và _set cực kỳ hữu ích khi bạn muốn xử lý logic bổ sung khi đọc hoặc ghi vào một thuộc tính không tồn tại trong table. Một ứng dụng phổ biến là đóng gói (wrapper) các hàm từ file DLL để tự động hóa việc chuyển đổi kiểu dữ liệu và giải phóng bộ nhớ.
Giả sử chúng ta có một thư viện ngoại vi trả về con trỏ (pointer), chúng ta muốn tự động chuyển con trỏ đó thành chuỗi và giải phóng bộ nhớ ngay lập tức:
namespace MyNativeLib {
// Giả lập hàm API từ DLL
var dllApi = {
fetchData = function(id) { return "Dữ liệu mẫu từ " ++ id };
releaseMem = function(ptr) { /* logic giải phóng */ };
};
class DataRequester {
ctor(id) {
this.id = id;
};
@_meta;
}
namespace DataRequester {
_meta = {
_get = function(key) {
var func = dllApi[key];
if(!func) return null;
return function(...) {
var val = func(owner.id, ...);
// Thực hiện logic xử lý dữ liệu thô tại đây
return val;
};
}
}
}
}
var client = MyNativeLib.DataRequester("ID_001");
// Mặc dù client không có hàm fetchData, _get sẽ chuyển hướng và gọi từ dllApi
import console;
console.log(client.fetchData());
console.pause();
Tạo Proxy Table để giám sát dữ liệu
Bạn có thể tạo một lớp trung gian (proxy) để theo dõi mọi thao tác đọc/ghi trên một bảng dữ liệu:
function createWatcher(targetTable) {
var storage = targetTable;
var behavior = {
_get = function(key) {
import console;
console.log("Đang đọc thuộc tính: " + key);
return storage[key];
};
_set = function(key, value) {
import console;
console.log("Đang cập nhật " + key + " thành: " + value);
storage[key] = value;
}
};
return {@behavior};
}
var myData = { score = 100 };
var proxy = createWatcher(myData);
var s = proxy.score; // In ra: Đang đọc thuộc tính: score
proxy.score = 95; // In ra: Đang cập nhật score thành: 95
Danh sách các Metamethod phổ biến
| Metamethod | Mô tả | Tương ứng toán tử/hàm |
|---|---|---|
_get |
Kích hoạt khi đọc một khóa không tồn tại. | obj.key hoặc obj["key"] |
_set |
Kích hoạt khi gán giá trị cho một khóa không tồn tại. | obj.key = val |
_tostring |
Xác định cách chuyển đổi đối tượng sang chuỗi. | tostring(obj) |
_add, _sub, _mul, _div |
Các phép toán số học cơ bản. | +, -, *, / |
_len |
Lấy độ dài của đối tượng. | Toán tử # |
_call |
Cho phép gọi table như một hàm. | obj() |
Tối ưu hóa với util.metaProperty
Khi một đối tượng có quá nhiều thuộc tính cần kiểm soát, việc sử dụng if-else trong _get hoặc _set sẽ làm mã nguồn trở nên rối rắm. Thư viện util.metaProperty giúp định nghĩa các thuộc tính (properties) một cách sạch sẽ hơn, tương tự như getter/setter trong các ngôn ngữ hướng đối tượng khác.
import util.metaProperty;
import console;
class UIWidget {
ctor() {
this.internalText = "Mặc định";
};
@_metaProperty;
}
namespace UIWidget {
_metaProperty = ..util.metaProperty(
content = {
_get = function() {
console.log("Đang lấy nội dung...");
return owner.internalText;
};
_set = function(v) {
console.log("Đang đặt nội dung mới...");
owner.internalText = v;
};
};
visible = {
_get = function() { return true; };
}
);
}
var widget = UIWidget();
widget.content = "Xin chào aardio"; // Kích hoạt _set
console.log(widget.content); // Kích hoạt _get
console.pause();