Ruby Metaprogramming Is Even Cooler Than It Sounds (1/2)
Bài đăng này đã không được cập nhật trong 3 năm
Bạn có thể thường nghe rằng metaprogramming là những thứ chỉ có "Ruby ninjas" sử dụng, và không đơn giản đối với những người mới bắt đầu với ruby. Nhưng sự thật thì metaprogramming không phải là thứ đáng sợ đến như vậy. Bài viết này sẽ thay đổi suy nghĩ đó để khoảng cách giữa metaprogramming và số đông những lập trình viên ruby trở nên gần hơn và chúng ta đề có thể khai thác những điều thú vị từ metaprogramming đang chờ đón.
Metaprogramming
Metaprogramming là một kỹ thuật code writing code, từ chính đoạn code chúng ta viết lại tự viết những đoạn code khác một cách tự động khi biên dịch chương trình. Điều này có nghĩa là bạn có thể định nghĩa phương thức hay class trong quá trình biên dịch. Metaprogramming có thể sửa đổi các class, tạo ra các phương thức nếu chúng chưa tồn tại trong khi biên dịch, hay viết code giúp tránh được vi phạm DRY và còn nhiều hơn thế nữa.
The basics
Chúng ta hãy hiểu rõ những kiến thức cơ bản trước khi đi sâu vào tìm hiểu về metaprogramming. Hãy bắt đầu bằng một ví dụ, bạn có lẽ đã đoán được đoạn code dưới đây làm gì:
class Developer
def self.backend
"I am backend developer"
end
def frontend
"I am frontend developer"
end
end
Một class được định nghĩa với hai phương thức. Phương thức thứ nhất là class method và phương thức còn lại là instance method (có thể hiểu đơn giản class method là phương thức được gọi từ chính class đó và instance method phải gọi qua một thực thể của class đó). Đây có thể là những đoạn code rất đơn giản bạn viết hằng ngày, nhưng đằng sau nó lại ẩn chứa những điều mà chúng ta cần phải hiểu rõ trước khi chúng ta tìm hiểu sâu hơn. Điều đáng chú ý ở đây đó là class Developer
cũng chính là một object. Trong Ruby mọi thứ đều là một đối tượng nào đó, bao gồm cả class. Class Developer
là một thực thể của class Class
. Dưới đây là mô hình object của Ruby giúp chúng ta hiểu hơn
p Developer.class # Class
p Class.superclass # Module
p Module.superclass # Object
p Object.superclass # BasicObject
Một điều quan trọng đó là ý nghĩa của self
. Phương thức frontend
là regular method, phương thức được sử dụng bởi các thực thể của class Developer
, nhưng tại sao backend
lại là class method. self
luôn luôn tham chiếu tới một object nhưng đối tượng đó có thay đổi dựa trên code được thực thi. Ví dụ như trong một định nghĩa class, self
tham chiếu tới chính nó hay là một thực thể của class Class
.
class Developer
p self
end
# Developer
Trong thực thể method, self
tham chiếu tới một đối tượng của class đó.
class Developer
def frontend
self
end
end
p Developer.new.frontend
# #<Developer:0x2c8a148>
Trong class method, self
tham chiếu tới chính class đó theo một cách nào đó (sẽ được nhắc đến trong phần dưới bài viết)
class Developer
def self.backend
self
end
end
p Developer.backend
# Developer
Vậy class method là gì? Trước khi trả lời câu hỏi này chúng ta phải nhắc đến một khái niệm khác được gọi là metaclass, được biết đến như cốt lõi của class và eigenclass. Phương thức của class frontend
chúng ta định nghĩa phía trên không là gì cả nhưng lại là một thực thể method được định nghĩa trong metaclass cho đối tượng Developer
. Một metaclass là những cơ bản mà Ruby tạo ra và đưa vào trong hệ thống cấu trúc kế thừa để lưu lại các phương thức của class, do đó không có quan hệ với thực thể mà được tạo ra từ class đó.
Metaclasses
Mỗi một đối tượng trong Ruby để có riêng metaclass của riêng nó. Nó là cái vô hình với lập trình viên nhưng nó luôn tồn tại và bạn có thể sử dụng nó rất dễ dàng. Và như vậy Developer
là một đối tượng thuần và nó có metaclass của riêng nó. Ví dụ sau đây sẽ tạo ra một đối tượng của class String
và điểu khiển metaclass của object đó:
example = "I'm a string object"
def example.something
self.upcase
end
p example.something
# I'M A STRING OBJECT
Chúng ta vừa mới thêm vào một singleton phương thức something
cho một đối tượng. Sự khác biệt giữa class method và singleton method đó là class method thì được gọi bởi mội thực thể của class đó còn singleton method thì chỉ được gọi bởi một thực thể duy nhất mà nó được thêm vào. Class method thì có phạm vi rộng rãi trong khi singleton thì ngược lại, nhưng cả hai kiểu đều được thêm vào một metaclass của một đối tượng.
Ví dụ ngay trên có thể được viết lại giống như sau:
example = "I'm a string object"
class << example
def example.something
self.upcase
end
end
Cú pháp là khác nhau nhưng kết quả thì giống nhau. Bây giờ hãy quay lại ví dụ trước nơi mà chúng ta khởi tạo class Developer
và viết lại theo cú pháp khác để định nghĩa một class method:
class Developer
def self.backend
"I am backend developer"
end
end
Đây là định nghĩa căn bản mà hầu hết mọi người sử dụng.
def Developer.backend
"I am backend developer"
end
Và đây cũng là đoạn code tương tự, chúng ta định nghĩa class method cho Developer
. Chúng ta không sử dụng self
nhưng có kết quả tương tự.
class Developer
class << self
def backend
"I am backend developer"
end
end
end
Lần nữa chúng ta định nghĩa một class method, nhưng cú pháp giống như chúng ta đã từng định nghĩa một singleton method cho một đối tượng String
ở ví dụ trên. Bạn có thể để ý rằng chúng ta sử dụng self
ở ví dụ này thay vì trỏ tới thực thể Developer
.
class << Developer
def backend
"I am backend developer"
end
end
Bằng cách định nghĩa một block như trên, chúng ta làm cho self
trỏ tới metaclass của Developer
cho toàn bộ block. Do đó kết quả là chúng ta đã thêm backend
vào metaclass của Developer
thay vì chính class đó.
Hình ảnh sau sẽ cho chúng ta thấy ý nghĩa của metaclass trong đồ thị thừa kế:
Như bạn có thể thấy trong những ví dụ trước, chúng ta không có bằng chứng thực sự về sự tồn tại của metaclass. Nhưng chúng ta có thể bằng cách nào đó thấy được sự tồn tại của class vô hình này:
class Object
def metaclass_example
class << self
self
end
end
end
Nếu chúng ta định nghĩa một thực thể method trong Object class( chúng ta có thể làm việc này bất cứ lúc nào và bất cứ class nào - đó là một thứ rất hay của metaprogramming), chúng ta sẽ có một tham chiếu self
trỏ tới đối tượng Object
bên trong nó. Chúng ta có thể sử dụng cú pháp class << self
để thay đổi con trỏ của self tới metaclass của đối tượng hiện thời.
Như vậy chúng ta đã có thể hiểu được các khái niệm cơ bản cần thiết về class, các method, tham chiếu self
, metaclass. Hẹn gặp lại các bạn ở bài viết sau sẽ phần nào cho thấy được metaprogramming thực sự là gì và sử dụng chúng ra sao.
Tham khảo Ruby Metaprogramming
All rights reserved