Ruby send vs __send__
Я понимаю концепцию some_instance.send
, но я пытаюсь понять, почему вы можете назвать это двумя способами. Ruby Koans подразумевает, что есть некоторая причина, помимо предоставления множества разных способов сделать то же самое. Вот два примера использования:
class Foo
def bar?
true
end
end
foo = Foo.new
foo.send(:bar?)
foo.__send__(:bar?)
Кто-нибудь есть об этом?
Ответы
Ответ 1
Некоторые классы (например, стандартный класс сокетов библиотеки) определяют свой собственный метод send
, который не имеет ничего общего с Object#send
. Поэтому, если вы хотите работать с объектами любого класса, вам нужно использовать __send__
, чтобы быть в безопасности.
Теперь возникает вопрос, почему существует send
, а не только __send__
. Если бы было только __send__
, имя send
могло бы использоваться другими классами без какой-либо путаницы. Причиной этого является то, что send
существовал первым, и только позже было осознано, что имя send
также может быть полезно использовать в других контекстах, поэтому __send__
был добавлен (то же самое, что произошло с id
и object_id
кстати).
Ответ 2
Если вам действительно нужно send
вести себя так, как обычно, вы должны использовать __send__
, потому что он не будет (он не должен) быть переопределенным. Использование __send__
особенно полезно при метапрограммировании, когда вы не знаете, какие методы контролирует класс. Это могло бы превысить 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 выдаст предупреждение:
предупреждение: переопределение `__send__ 'может вызывают серьезные проблемы.
В некоторых случаях, когда было бы полезно переопределить send
, было бы подходящим это имя, например, передача сообщений, классы сокетов и т.д.
Ответ 3
__send__
существует, поэтому он не может быть переписан случайно.
Что касается того, почему существует send
: я не могу говорить никому, но object.send(:method_name, *parameters)
выглядит лучше, чем object.__send__(:method_name, *parameters)
, поэтому я использую send
, если мне не нужно использовать __send__
.
Ответ 4
Помимо того, что вам уже говорили другие, и что сводится к утверждению, что send
и __send__
являются двумя псевдонимами того же метода, вас может заинтересовать третья, иногда другая возможность, которая public_send
, Пример:
A, B, C = Module.new, Module.new, Module.new
B.include A #=> error -- private method
B.send :include, A #=> bypasses the method privacy
C.public_send :include, A #=> does not bypass privacy
Обновление. Поскольку методы Ruby 2.1, Module#include
и Module#extend
становятся общедоступными, поэтому приведенный выше пример больше не работает.
Ответ 5
Основное различие между send, __send__
и public_send заключается в следующем.
- send и
__send__
технически совпадают с используемыми для вызова метода Object, но основное отличие состоит в том, что вы можете переопределить метод send без какого-либо предупреждения, а при переопределении __send__
появляется предупреждающее сообщение
предупреждение: переопределение __send__
может вызвать серьезные проблемы
Это связано с тем, что во избежание конфликтов, особенно в гемах или библиотеках, когда контекст, в котором он будет использоваться, неизвестен, всегда используйте __send__
вместо send.
- Разница между send (или
__send__
) и public_send заключается в том, что send/__send__
может вызывать объекты private, а public_send cant.
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__ или send.