+2

Metaprogramming - send

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 methodprivate 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

Viblo
Let's register a Viblo Account to get more interesting posts.