также вызовем 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 есть две библиотеки, которые предлагают решение задачи о делегировании или перенаправлении вызовов методов другому объекту. Они называются delegate
и forwardable
; мы рассмотрим обе.
Библиотека delegate
предлагает три способа решения задачи. Класс SimpleDelegator
полезен, когда объект, которому делегируется управление (делегат), может изменяться на протяжении времени жизни делегирующего объекта. Чтобы выбрать объект-делегат, используется метод __setobj__
.
Однако мне этот способ представляется слишком примитивным. Поскольку я не думаю, что это существенно лучше, чем то же самое, сделанное вручную, задерживаться на классе SimpleDelegator
не стану.
Метод верхнего уровня DelegateClass
принимает в качестве параметра класс, которому делегируется управление. Затем он создает новый класс, которому мы можем унаследовать. Вот пример создания класса Queue
, который делегирует объекту Array
:
require 'delegate'
class MyQueue < DelegateClass(Array)
def initialize(arg=[])
super(arg)
end
alias_method :enqueue, :push
alias_method :dequeue, :shift
end
mq = MyQueue.new
mq.enqueue(123)
mq.enqueue(234)
p mq.dequeue # 123
p mq.dequeue # 234
Можно также унаследовать класс Delegator
и реализовать метод __getobj__
; именно таким образом реализован класс SimpleDelegator
. При этом мы получаем больший контроль над делегированием.
Но если вам необходим больший контроль, то, вероятно, вы все равно осуществляете делегирование на уровне отдельных методов, а не класса в целом. Тогда лучше воспользоваться библиотекой forwardable
. Вернемся к примеру очереди: