Metaprogramming - send
This post hasn't been updated for 8 years
Chào các bạn, chúng ta lại gặp nhau trong series tìm hiểu về metaprogramming
trong ruby. Trong bài này mình sẽ nói về phương thức send
.
h3. **1. Tại sao lại là @send@**
Nếu bạn đang học ruby, chắc hẳn bạn sẽ biết ruby là một ngôn ngữ hướng đối tượng. Bất kể khi nào bạn gọi một phương thức của một đối tượng, tức là bạn đang gửi một "message" đến đối tượng đó. Và "message" chính là tên của phương thức. Ví dụ: Khi bạn gọi send("to_s")
trên một đôí tượng thuộc class Fixnum
tức là bạn đang gửi một message có nội dung là "to_s" đến đối tượng đó.
Phương thức send
cho phép chúng ta gọi một phương thức tới một đối tượng cụ thể mà chúng ta chưa xác định được chính xác tên của phương thức đó cho đến khi nó được thực thi.
Ngoài ra, bạn chắc hẳn cũng biết đến phương thức call
của ruby. Rõ ràng "call" nghe có vẻ hợp lý hơn "send", chúng ta nói "gọi một phương thức" dễ hiểu hơn khi nói "gửi một phương thức", ít ra nó hợp hơn khi dịch ra tiếng việt... Nhưng, như tôi đã nói, bạn chắc hẳn cũng biết đến block
, proc
hay lambda
, có nhiều thứ để nói về những thứ trên nhưng tóm lại, bằng cách này hay cách khác, chúng đều có chung một mục đích là để khai báo một đoạn mã nào đó. Và call
sẽ dùng để thực thi đoạn mã đó.
Ta có thể thấy sự khác nhau cơ bản ở đây, send
sẽ thực thi một phương thức; còn call
sẽ thực thi một đoạn mã cụ thể. Khi đoạn mã đó được đặt trong một phương thức, hiển nhiên ta sẽ có công thức toán học sau:
object.send(:xxx) = object.method(:xxx).call
Bằng cách nhìn và đoán, ta có thể thấy một điều là sử dụng send
sẽ nhanh hơn call
. Thật đơn giản!
h3. **2. Sử dụng @send@**
Sau một hồi lảm nhảm về send
, chúng ta cùng đi đến phần cần thiết nhất cho người mới bắt đầu đọc về send
: khi nào thì nên sử dụng send
Nếu bạn lướt qua phần 1, bạn có thể chỉ cần biết: chúng ta dùng send
thay cho cách gọi phương thức thông thường khi muốn tùy biến tên phương thức cần gọi; với điều kiện là phương thức đó phải tồn tại trong đối tượng chứa nó.
Ví dụ, tôi có một class
Person như sau:
class Person
attr_accessor :name, :age, :date_of_birth, :sex, :phone, :email, :citizenship, :address
def is_name_nil?
self.name.nil?
end
def age_is_correct?
is_unsigned_int? self.age
end
def is_communist? country
country.include? self.citizenship
end
private
def is_unsigned_int? string
Integer(string || "")
true
rescue ArgumentError
false
end
end
Giả sử tôi muốn kiểm tra xem trong các thông tin của người đó có thông tin nào không tồn tại hay sai sự thật hay không, cụ thể là "name", tôi sẽ phải gọi đến hàm is_name
để kiểm tra.
object = Person.new
object.is_name_nil?
...
Nếu class của bạn chỉ có một thuộc tính duy nhất hay nhiều nhất là 2 thuộc tính, việc kiểm tra bằng cách này vẫn chấp nhận được. Nhưng nếu có từ 3 thuộc tính trong class, rõ ràng kiểm tra từng thuộc tính một sẽ là quá rắc rối. Vẫn giữ nguyên ý tưởng rằng trong class đó đã có sẵn các phương thức để kiểm tra sự tồn tại của các thuộc tính của một đối tượng, ta có thể sử dụng send
thay vì phải viết tất cả các hàm kiểm tra ra:
%w(name age date_of_birth sex phone email citizenship address).each do |arg|
object.send "is_#{arg}_nil?"
end
Nếu muốn truyền tham số vào hàm send
, ta chỉ việc khai báo tham số ngay sau tên hàm, cách nhau bởi một dấu ",". Ví dụ:
communist_nations = ["Vietnam", "Chinese", "Cuba", "Laos", "North Korea"]
object.send "is_communist?", communist_nations
h3. **3. Ưu, nhược điểm của @send@**
Như tôi đã nói, ưu điểm dễ thấy nhất của send
là cho phép chúng ta gọi phương thức của một đối tượng khi chỉ cần biết tên phương thức đó dưới dạng string
hoặc symbol
. Nó cho phép chúng ta tùy biến code, giúp rút ngắn số dòng code cần để thực thi các câu lệnh.
Nhưng, theo tôi thì ưu điểm lớn nhất của send
chính là cho phép chúng ta khả năng truy cập protected method
và private method
ngay cả khi ở bên ngoài class. Như trong ví dụ bên trên, tôi hoàn toàn có thể gọi phương thức is_unsigned_int?
nhờ vào send
, điều mà cách gọi thông thường không thể làm được.
new_person = Person.new
new_person.age = "18"
new_person.is_unsigned_int? new_person.age
=> NoMethodError: private method 'is_unsigned_int?' called for #<Person:0x0055da8526b6e0 .@age="18">
new_person.send :is_unsigned_int?, new_person.age
=> true
Ưu điểm lớn nhất cũng chính là nhược điểm lớn nhất của send
, đó là phá vỡ các quy tắc cơ bản của hướng đối tượng trong ruby, ta có thể gọi các private method
ngay cả bên ngoài class, điều này cực kì nguy hiểm và nên hạn chế sử dụng. Để an toàn hơn phần nào đó, ta có thể sử dụng public_send
thay cho send
, phương thức trên sẽ chỉ truy cập được các phương thức được khai báo là public
.
Ngoài ra, send còn có một nhược điểm khác mà tôi đã đề cập đến trong bài viết trước, đó là hacker có thể truy cập vào các phương thức của chúng ta, lấy ra các dữ liệu nguy hiểm.
Bài viết của mình xin được tạm dừng tại đây, hi vọng nó đã giúp bạn phần nào hiểu hơn cách sử dụng hàm send
. Hẹn gặp lại các bạn trong các bài viết tiếp theo.
All Rights Reserved