Ruby gửi vs __send__


151

Tôi hiểu khái niệm về some_instance.sendnhưng tôi đang cố gắng tìm hiểu tại sao bạn có thể gọi cả hai cách này. Ruby Koans ngụ ý rằng có một số lý do ngoài việc cung cấp nhiều cách khác nhau để làm điều tương tự. Dưới đây là hai ví dụ về cách sử dụng:

class Foo
  def bar?
    true
  end
end

foo = Foo.new
foo.send(:bar?)
foo.__send__(:bar?)

Bất cứ ai có bất kỳ ý tưởng về điều này?

Câu trả lời:


242

Một số lớp (ví dụ lớp socket của thư viện chuẩn) định nghĩa sendphương thức riêng của chúng không liên quan gì Object#send. Vì vậy, nếu bạn muốn làm việc với các đối tượng của bất kỳ lớp nào, bạn cần sử dụng __send__để ở bên an toàn.

Bây giờ để lại câu hỏi, tại sao có sendvà không chỉ __send__. Nếu chỉ có __send__tên sendcó thể được sử dụng bởi các lớp khác mà không có sự nhầm lẫn. Lý do cho điều đó là sendtồn tại đầu tiên và chỉ sau đó người ta nhận ra rằng tên này sendcũng có thể được sử dụng một cách hữu ích trong các bối cảnh khác, vì vậy __send__đã được thêm vào (đó là điều tương tự xảy ra idobject_idnhân tiện).


8
Ngoài ra, BasicObject (được giới thiệu trong Ruby 1.9) chỉ có __send__, không có send.
Andrew Marshall

Câu trả lời tốt. Có thể thậm chí tốt hơn nếu nó được đề cập public_send, thường được ưa thích hơn senddù sao.
Marc-André Lafortune

31

Nếu bạn thực sự cần sendphải cư xử như bình thường, bạn nên sử dụng __send__, vì nó sẽ không (không nên) bị quá tải. Việc sử dụng __send__đặc biệt hữu ích trong siêu lập trình, khi bạn không biết phương thức mà lớp đang thao tác xác định. Nó có thể có overriden send.

Đồng hồ đeo tay:

class Foo
  def bar?
    true
  end

  def send(*args)
    false
  end
end

foo = Foo.new
foo.send(:bar?)
# => false
foo.__send__(:bar?)
# => true

Nếu bạn ghi đè __send__, Ruby sẽ phát ra cảnh báo:

cảnh báo: xác định lại `__send__ 'có thể gây ra vấn đề nghiêm trọng

Một số trường hợp sẽ hữu ích khi ghi đè sendsẽ là nơi tên đó phù hợp, như chuyển tin nhắn, lớp socket, v.v.


9

__send__ tồn tại để nó không thể được viết quá mức một cách tình cờ.

Về lý do tại sao sendtồn tại: Tôi không thể nói cho bất kỳ ai khác, nhưng object.send(:method_name, *parameters)trông đẹp hơn object.__send__(:method_name, *parameters), vì vậy tôi sử dụng sendtrừ khi tôi cần sử dụng __send__.


6

Ngoài những gì người khác đã nói với bạn, và những gì sôi nổi khi nói điều đó send__send__là hai bí danh của cùng một phương pháp, bạn có thể quan tâm đến thứ ba, một khả năng khác nhau, đó là public_send. Thí dụ:

A, B, C = Module.new, Module.new, Module.new
B.include A #=> error -- private method
B.send :include, A #=> bypasses the method's privacy
C.public_send :include, A #=> does not bypass privacy

Cập nhật: Vì Ruby 2.1 Module#includeModule#extendcác phương thức trở nên công khai, vì vậy ví dụ trên sẽ không hoạt động nữa.


0

Sự khác biệt chính giữa gửi __send__và public_send là như sau.

  1. gửi và về mặt __send__kỹ thuật giống như được sử dụng để gọi phương thức của Object, nhưng điểm khác biệt chính là bạn có thể ghi đè phương thức gửi mà không có bất kỳ cảnh báo nào và khi bạn ghi đè lên __send__thì có một thông báo cảnh báo

cảnh báo: xác định lại __send__có thể gây ra vấn đề nghiêm trọng

Điều này là do để tránh xung đột, đặc biệt trong đá quý hoặc thư viện khi bối cảnh nơi nó sẽ được sử dụng không xác định, luôn luôn sử dụng __send__thay vì gửi.

  1. Sự khác biệt giữa send (hoặc __send__) và public_send là gửi / __send__có thể gọi các phương thức riêng tư của đối tượng và public_send không thể.
class Foo
   def __send__(*args, &block)
       "__send__"
   end
   def send(*args)
     "send"
   end
   def bar
       "bar"
   end
   private
   def private_bar
     "private_bar"
   end
end

Foo.new.bar #=> "bar"
Foo.new.private_bar #=> NoMethodError(private method 'private_bar' called for #Foo)

Foo.new.send(:bar) #=> "send"
Foo.new.__send__(:bar) #=> "__send__"
Foo.new.public_send(:bar) #=> "bar"

Foo.new.send(:private_bar) #=> "send"
Foo.new.__send__(:private_bar) #=> "__send__"
Foo.new.public_send(:private_bar) #=> NoMethodError(private method 'private_bar' called for #Foo)

Cuối cùng, hãy thử sử dụng public_send để tránh cuộc gọi trực tiếp đến phương thức riêng tư thay vì sử dụng __send__ hoặc gửi.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.