#ruby #inheritance #decorator
#ruby #наследование #декоратор
Вопрос:
У меня есть класс Base
и два класса Derived
, Derived2
которые наследуются от Base
. У каждого foo
из них есть определенная в них функция.
У меня также есть модуль Gen
, который редактируется prepend
Base
. Это также prepend
относится к Derived2
, но не к Derived
.
Когда я вызываю foo
экземпляр Derived2
, результат выглядит так, как если Gen
бы модуль был только prepend
Base
отредактирован, а не Derived2
также. Это ожидаемое поведение?
Вот код для вышеупомянутого сценария:
module Gen
def foo
val = super
'[' val ']'
end
end
class Base
prepend Gen
def foo
"from Base"
end
end
class Derived < Base
def foo
val = super
val "from Derived"
end
end
class Derived2 < Base
prepend Gen
def foo
val = super
val "from Derived"
end
end
Base.new.foo # => "[from Base]"
Derived.new.foo # => "[from Base]from Derived"
Derived2.new.foo # => "[from Base]from Derived"
Я ожидал, что последнее из приведенного выше утверждения будет выведено:
[[from Base]from Derived]
Ответ №1:
Чтобы помочь вам понять, существует метод Class#ancestors
, который сообщает вам порядок, в котором будет выполняться поиск метода. В этом случае:
Base.ancestors # => [Gen, Base, Object, Kernel, BasicObject]
Derived.ancestors # => [Derived, Gen, Base, Object, Kernel, BasicObject]
Derived2.ancestors # => [Gen, Derived2, Gen, Base, Object, Kernel, BasicObject]
Поэтому, когда вы вызываете метод для объекта, который является экземпляром указанного класса, этот метод будет найден в соответствующем списке в таком порядке.
- Добавление помещает модуль перед этим списком.
- Наследование помещает родительскую цепочку в конец дочерней (не совсем, но для простоты).
super
просто говорит: «Иди дальше по цепочке и найди мне тот же метод».
Для Base
этого у нас есть две реализации foo
— that in Base
и that in Gen
. Тот Gen
, который будет найден первым, поскольку модуль был добавлен. Поэтому вызов его на экземпляре Base
вызовет Gen#foo
= [S]
, который также будет искать цепочку (через super
) = [from Base]
.
Например Derived
, модуль не был добавлен, и у нас есть наследование. Таким образом, первой найденной реализацией является то, что in Derived
= Sfrom Derived
и super
будет искать остальную часть цепочки, которая исходит из Base
(иначе приведенный выше абзац воспроизводится) = [from Base]from Derived
.
Например Derived2
, модуль добавляется, поэтому сначала будет найден метод = [S]
. Затем super
там будет найден следующий foo
в Derived2
= [Sfrom Derived]
, и super
там снова разыграется ситуация для Base
= [[from Base]from Derived]
.
РЕДАКТИРОВАТЬ: кажется, что до самого недавнего времени prepend
сначала выполнялся поиск в цепочке предков и добавлялся модуль, только если он еще не присутствует (аналогично include
). Чтобы сделать это еще более запутанным, если вы сначала создадите родительский элемент, унаследуете от него, добавите в дочерний элемент, а затем добавите в родительский элемент, вы получите результат более новых версий.
Комментарии:
1. Я знаю, как
prepend
это должно работать. Но цепочка предков оказалась не такой, как вы напечатали выше. 1.Base.ancestors # => [Gen, Base, Object, Kernel, BasicObject]
2.Derived.ancestors # => [Derived, Gen, Base, Object, Kernel, BasicObject]
3.Derived2.ancestors # => [Derived2, Gen, Base, Object, Kernel, BasicObject]
Я на Ruby 2.2.4.2. Знаете ли вы, в какой версии было произведено это изменение поведения?
3. @CppNoob Сейчас я скачаю 2.2.5, но, похоже, он изменился после 2.3 . Это не отражено в документации, и я также не смог найти его в журнале изменений. Похоже, это регрессия, я проведу еще несколько исследований и сообщу об этом.
4. @CppNoob, да — это 2.3.0
5. @CppNoob, это было «нормальным» поведением вплоть до версии 2.3.0.
[Gen, Derived2, Gen, Base, Object, Kernel, BasicObject]
Версия поставляется с 2.3.0, то есть. В документации явно указано, что его не следует добавлять, если он уже присутствует в цепочке. Похоже, это регрессия, я создал заявку .