Подтвердить что ты не робот

Рекомендуемый подход к обезглавливанию класса в рубине

Я заметил, что есть два общих способа обезьяны патч класса в рубине:

Определите новые члены в классе следующим образом:

class Array
   def new_method
     #do stuff
   end
end

И вызов класса_eval в объекте класса:

Array.class_eval do
   def new_method
      #do stuff
   end
end

Мне интересно, есть ли разница между ними и есть ли преимущества в использовании одного подхода над другим?

4b9b3361

Ответ 1

Честно говоря, я использовал 1-ю форму (повторное открытие класса), поскольку она кажется более естественной, но ваш вопрос заставил меня провести некоторое исследование по этому вопросу и вот результат.

Проблема с повторным открытием класса заключается в том, что он будет молча определять новый класс, если исходный, который вы намеревались повторно открыть, по какой-то причине не был определен на данный момент. Результат может быть другим:

  • Если вы не переопределяете какие-либо методы, а только добавляете новые, и определяется исходная реализация (например, файл, где первоначально задан класс), все будет нормально.

  • Если вы переопределите некоторые методы и оригинал будет загружен позже, ваши методы будут переопределены исходными версиями.

  • Самый интересный случай - когда вы используете стандартную автозагрузку или какой-то причудливый механизм перезагрузки (например, тот, который используется в Rails) для загрузки/перезагрузки классов. Некоторые из этих решений полагаются на const_missing, который вызывается, когда вы ссылаетесь на константу undefined. В этом случае механизм автозагрузки пытается найти определение undefined class и загрузить его. Но если вы определяете класс самостоятельно (пока вы планируете повторно открыть уже определенный), он больше не будет "отсутствовать", и оригинал может никогда не загружаться вообще, поскольку механизм автозагрузки не будет запущен.

С другой стороны, если вы используете class_eval, вы будете немедленно уведомлены, если класс не определен в данный момент. Кроме того, поскольку вы ссылаетесь на класс, когда вы вызываете его метод class_eval, любой механизм автозагрузки будет иметь возможность определить определение класса и загрузить его.

Имея это в виду class_eval, кажется, лучший подход. Хотя, я был бы рад услышать какое-то другое мнение.

Ответ 2

Область

Одна большая разница, которая, как мне кажется, KL-7 не указала, - это область, в которой будет интерпретировать ваш новый код:

Если вы (повторно) открываете класс для управления им, новый добавленный код будет интерпретироваться в области (оригинального) класса.
Если вы используете Module # class_eval для управления классом, новый добавленный код будет интерпретироваться в области, окружающей ваш вызов, на #class_eval и будет НЕ помните о классе. Если кто-то не знает, это поведение может быть противоречивым и привести к ошибкам с отладкой.

CONSTANT    = 'surrounding scope'

# original class definition (uses class scope)
class C
  CONSTANT  = 'class scope'

  def fun()  p CONSTANT  end
end
C.new.fun    # prints: "class scope"


# monkey-patching with #class_eval: uses surrounding scope!
C.class_eval do
  def fun()  p CONSTANT  end
end
C.new.fun    # prints: "surrounding scope"


# monkey-patching by re-opening the class: uses scope of class C
class C
  def fun()  p CONSTANT  end
end
C.new.fun    # prints: "class scope"