Một Phương Pháp "Tiểu Xảo" Để Triển Khai Lập Trình Hướng Đối Tượng Trong Ngôn Ngữ Kịch Bản
Ngày hôm nay, bộ biên dịch kịch bản mà tôi đang phát triển cùng với máy ảo phía trước đã hoàn thành toàn bộ. Đây là một thành tựu đáng tự hào! Giống như Lua, về kiểu dữ liệu phức tạp, tôi chỉ hỗ trợ duy nhất kiểu “table” - một cấu trúc dữ liệu đa năng có thể vừa đóng vai trò như mảng (array) vừa hoạt động như bản băm (hash map). Trong cộng đồng sử dụng Lua, nhiều người thường tận dụng table để mô phỏng các lớp (class). Tuy nhiên, Lua chỉ cung cấp một mức hỗ trợ rất hạn chế thông qua cú pháp đặc biệt: như tài liệu chính thức ghi nhận, đoạn mã function t.a.b.c:f (...) ... end
sẽ được tự động chuyển đổi thành t.a.b.c.f = function (self, ...) ... end
.
Theo kinh nghiệm sử dụng Lua của tôi, sự chuyển đổi này mang lại lợi ích không quá lớn - chủ yếu chỉ giúp tiết kiệm việc phải viết thủ công tham số self
. Trong thiết kế ngôn ngữ kịch bản mới này, tôi đã dành thời gian nghiên cứu kỹ lưỡng để cải tiến khả năng hỗ trợ lập trình hướng đối tượng.
Hãy cùng xem đoạn mã ví dụ sau (được viết bằng ngôn ngữ do tôi tự thiết kế):
|
|
Đây là một ví dụ cực kỳ đơn giản nhưng chứa đựng nhiều ý tưởng thiết kế thú vị. Hàm print
trong ví dụ là một hàm toàn cục được tôi đăng ký sẵn trong chương trình kiểm thử. Ý tưởng chính ở đây là các phương thức thành viên sẽ được lưu trữ trong một bảng (table) riêng biệt. Trong trường hợp này, hai hàm A.sum
và A.print
được đặt bên trong bảng A
.
Khi tạo đối tượng a = {A, .a=100, .b=200}
, phần tử đầu tiên a[0]
sẽ tự động trỏ đến bảng phương thức (vtbl) - đây là quy ước được thiết lập sẵn khi khởi tạo chuỗi bảng. Khi sử dụng toán tử ->
để gọi hàm (ví dụ a->sum()
), trình biên dịch sẽ tự động chuyển đổi thành a[0]->sum(a)
trong quá trình biên dịch.
Một điểm đặc biệt nữa là cách xử lý biến trong thân hàm: bất kỳ biến nào bắt đầu bằng dấu chấm (.) sẽ được mở rộng tự động thành _self.x
, với _self
là tham số đầu tiên của hàm. Mặc dù người dùng phải khai báo rõ ràng tham số này, nhưng có thể tự do đặt tên (với điều kiện tên phải bắt đầu bằng dấu gạch chân). Tương tự, biểu thức ->sum()
sẽ được mở rộng thành _self->sum()
. Nhờ đó, trong một phương thức thành viên, tất cả các biến bắt đầu bằng dấu chấm đều được hiểu là thuộc tính của đối tượng, còn các lời gọi hàm bắt đầu bằng ->
sẽ được coi là phương thức của lớp - tương tự như khái niệm hàm ảo (virtual function) trong C++. Nếu cần định nghĩa một phương thức tĩnh (không yêu cầu tham số self
), người dùng có thể gọi trực tiếp thông qua tên lớp như A.static_func()
.
Một cải tiến thú vị khác trong thiết kế của tôi là việc sử dụng quy ước đặt tên để phân biệt phạm vi biến: tất cả các biến bắt đầu bằng dấu gạch chân (_) sẽ được coi là biến cục bộ, trong khi các biến còn lại mặc định là biến toàn cục. Quy tắc này không chỉ giúp đơn giản hóa công việc của trình biên dịch mà còn tăng tính đọc hiểu của mã nguồn. Trong bối cảnh một ngôn ngữ không kiểm tra kiểu nghiêm ngặt (weakly-typed), cơ chế này còn giúp ngăn ngừa nhiều lỗi đánh máy thông thường.
Phương pháp tiếp cận này kết hợp giữa sự đơn giản của cấu trúc dữ liệu bảng với các quy ước đặt tên thông minh, tạo ra một hệ thống hướng đối tượng nhẹ nhàng nhưng đầy đủ tính năng. So với cách triển khai trong Lua, giải pháp của tôi cung cấp mức độ tích hợp tự nhiên hơn giữa các phương thức và thuộc tính, đồng thời duy trì được sự linh hoạt vốn có của các bảng dữ liệu.