Поведение «добавления` в иерархиях классов Ruby

#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, то есть. В документации явно указано, что его не следует добавлять, если он уже присутствует в цепочке. Похоже, это регрессия, я создал заявку .