Начиная с версии Ruby 1.6.2, можно также применять метод
Module#instance_method
для создания объектов
UnboundMethod
. С их помощью представляется метод, ассоциированный с классом, а не с конкретным объектом. Прежде чем вызывать объект
UnboundMethod
, нужно связать его с каким-то объектом. Результатом операции связывания является объект
Method
, который можно вызывать как обычно:
umeth = String.instance_method(:length)
m1 = umeth.bind("cat")
m1.call # 3
m2 = umeth.bind("caterpillar")
m2.call # 11
Явное связывание делает объект
UnboundMethod
интуитивно более понятным, чем
Method
.
11.2.7. Как работает включение модулей?
Когда модуль включается в класс, Ruby на самом деле создает прокси-класс, являющийся непосредственным родителем данного класса. Возможно, вам это покажется интуитивно очевидным, возможно, нет. Все методы включаемого модуля «маскируются» методами, определенными в классе.
module MyMod
def meth
"из модуля"
end
end
class ParentClass
def meth
"из родителя"
end
end
class ChildClass < ParentClass
include MyMod
def meth
"из потомка"
end
end
x = ChildClass.new p
p x.meth # Из потомка.
Выглядит это как настоящее наследование: все, что потомок переопределил, становится действующим определением вне зависимости от того, вызывается ли
include
до или после переопределения.
Вот похожий пример, в котором метод потомка вызывает
super
, а не просто возвращает строку. Как вы думаете, что будет возвращено?
# Модуль MyMod и класс ParentClass не изменились.
class ChildClass < ParentClass
include MyMod
def meth
"Из потомка: super = " + super
end
end
x = ChildClass.new
p x.meth # Из потомка: super = из модуля
Отсюда видно, что модуль действительно является новым родителем класса. А что если мы точно также вызовем
super
из модуля?
module MyMod
def meth
"Из модуля: super = " + super
end
end
# ParentClass не изменился.
class ChildClass < ParentClass
include MyMod
def meth
"Из потомка: super = " + super
end
end
x = ChildClass.new
p x.meth # Из потомка: super = из модуля: super = из родителя.
Метод
meth
, определенный в модуле
MyMod
, может вызвать
super
только потому, что в суперклассе (точнее, хотя бы в одном из его предков) действительно есть метод
meth
. А что произошло бы, вызови мы этот метод при других обстоятельствах?
module MyMod
def meth
"Из модуля: super = " + super
end
end
class Foo include MyMod
end
x = Foo.new
x.meth
При выполнении этого кода мы получили бы ошибку
NoMethodError
(или обращение к методу
method_missing
, если бы таковой существовал).
11.2.8. Опознание параметров, заданных по умолчанию
В 2004 году Ян Макдональд (Ian Macdonald) задал в списке рассылки вопрос: «Можно ли узнать, был ли параметр задан вызывающей программой или взято значение по умолчанию?» Вопрос интересный. Не каждый день он возникает, но от того не менее интересен.
Было предложено по меньшей мере три решения. Самое удачное и простое нашел Нобу Накада (Nobu Nakada). Оно приведено ниже:
def meth(a, b=(flag=true; 345))
puts "b равно #{b}, a flag равно #{flag.inspect}"
end
meth(123) # b равно 345, a flag равно true
meth(123,345) # b равно 345, a flag равно nil
meth(123,456) # b равно 456, a flag равно nil
Как видим, этот подход работает даже, если вызывающая программа явно указала значение параметра, совпадающее с подразумеваемым по умолчанию. Трюк становится очевидным, едва вы его увидите: выражение в скобках устанавливает локальную переменную
flag
в
true
, а затем возвращает значение по умолчанию 345. Это дань могуществу Ruby.
11.2.9. Делегирование или перенаправление
В Ruby есть две библиотеки, которые предлагают решение задачи о делегировании или перенаправлении вызовов методов другому объекту. Они называются