ทับทิมส่ง vs __send__


151

ฉันเข้าใจแนวคิดของsome_instance.sendแต่ฉันพยายามคิดออกว่าทำไมคุณสามารถเรียกสิ่งนี้ทั้งสองได้ Ruby Koans แปลว่ามีเหตุผลนอกเหนือจากการมอบวิธีการมากมายในการทำสิ่งเดียวกัน นี่คือตัวอย่างการใช้งานสองตัวอย่าง:

class Foo
  def bar?
    true
  end
end

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

ใครมีความคิดเห็นเกี่ยวกับเรื่องนี้บ้าง?

คำตอบ:


242

บางชั้นเรียน (เช่นห้องสมุดมาตรฐานของระดับซ็อกเก็ต) ของตัวเองกำหนดวิธีการที่มีอะไรจะทำอย่างไรกับsend Object#sendดังนั้นหากคุณต้องการทำงานกับวัตถุของคลาสใด ๆ คุณต้องใช้__send__เพื่อความปลอดภัย

ตอนนี้ที่ใบคำถามที่ว่าทำไมมีและไม่เพียงsend __send__หากมีเพียง__send__ชื่อเท่านั้นที่sendสามารถใช้คลาสอื่นได้โดยไม่มีความสับสน เหตุผลที่มีsendอยู่ก่อนและต่อมาก็รู้ว่าชื่อsendอาจใช้อย่างมีประโยชน์ในบริบทอื่น ๆ ดังนั้นจึง__send__ถูกเพิ่มเข้ามา (นั่นคือสิ่งเดียวกันที่เกิดขึ้นกับidและobject_idโดยวิธี)


8
นอกจากนี้BasicObject (แนะนำในทับทิม 1.9) มีเพียงไม่__send__ send
Andrew Marshall

คำตอบที่ดี. อาจจะดีกว่าถ้ามันพูดถึงpublic_sendซึ่งมักจะเป็นที่นิยมในเรื่องsendใด ๆ
Marc-André Lafortune

31

หากคุณจริงๆต้องsendไปทำตัวเหมือนปกติจะทำคุณควรใช้__send__เพราะมันจะไม่ได้ (มันไม่ควร) จะ overriden การใช้__send__มีประโยชน์อย่างยิ่งใน metaprogramming เมื่อคุณไม่ทราบว่าวิธีการเรียนการจัดการจะกำหนด มันอาจจะsendเกิน

ดู:

class Foo
  def bar?
    true
  end

  def send(*args)
    false
  end
end

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

หากคุณแทนที่__send__Ruby จะส่งคำเตือน:

คำเตือน: การกำหนด "__s____" ใหม่อาจทำให้เกิดปัญหาร้ายแรง

บางกรณีที่มีประโยชน์ในการลบล้างsendจะเป็นกรณีที่ชื่อนั้นเหมาะสมเช่นการส่งข้อความคลาสของซ็อกเก็ตเป็นต้น


9

__send__ มีอยู่จึงไม่สามารถเขียนทับได้โดยไม่ได้ตั้งใจ

สำหรับเหตุผลที่sendมีอยู่: ฉันไม่สามารถพูดสำหรับคนอื่น แต่object.send(:method_name, *parameters)รูปลักษณ์ที่ nicer กว่าobject.__send__(:method_name, *parameters)ดังนั้นฉันจะใช้sendจนกว่าฉันต้องการ__send__ที่จะใช้


6

นอกเหนือจากสิ่งที่คนอื่นบอกคุณแล้วและสิ่งที่เดือดลงไปบอกว่าsendและ__send__สองนามแฝงของวิธีการเดียวกันคุณอาจจะสนใจในสาม somwhat public_sendความเป็นไปได้ที่แตกต่างกันซึ่งเป็น ตัวอย่าง:

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

อัปเดต: เนื่องจาก Ruby 2.1 Module#includeและModule#extendเมธอดกลายเป็นสาธารณะดังนั้นตัวอย่างข้างต้นจะไม่ทำงานอีกต่อไป


0

ความแตกต่างที่สำคัญระหว่างการส่ง__send__และ public_send มีดังนี้

  1. ส่งและ__send__เป็นเทคโนโลยีเดียวกับที่ใช้ในการเรียกวิธีการของวัตถุ แต่ความแตกต่างหลักคือคุณสามารถแทนที่วิธีการส่งโดยไม่มีคำเตือนใด ๆ และเมื่อคุณแทนที่__send__แล้วมีข้อความเตือน

คำเตือน: การกำหนดใหม่__send__อาจทำให้เกิดปัญหาร้ายแรง

นี่เป็นเพราะเพื่อหลีกเลี่ยงความขัดแย้งโดยเฉพาะในอัญมณีหรือไลบรารีเมื่อไม่ทราบบริบทที่จะใช้ให้ใช้__send__แทนการส่ง

  1. ความแตกต่างระหว่าง send (หรือ__send__) และ public_send คือ send / __send__สามารถเรียกใช้เมธอดส่วนตัวของ object และ public_send ไม่สามารถทำได้
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)

ในตอนท้ายพยายามใช้ public_send เพื่อหลีกเลี่ยงการโทรโดยตรงไปที่วิธีส่วนตัวแทนการใช้ __send__ หรือส่ง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.