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

Как понять разницу между class_eval() и instance_eval()?

Foo = Class.new
Foo.class_eval do
  def class_bar
    "class_bar"
  end
end
Foo.instance_eval do
  def instance_bar
    "instance_bar"
  end
end
Foo.class_bar       #=> undefined method ‘class_bar’ for Foo:Class
Foo.new.class_bar   #=> "class_bar"
Foo.instance_bar       #=> "instance_bar"
Foo.new.instance_bar   #=> undefined method ‘instance_bar’ for #<Foo:0x7dce8>

Просто основанный на имени методов Я бы ожидал, что class_eval позволит вам добавить метод класса в Foo и instance_eval, чтобы вы могли добавить метод экземпляра в Foo. Но они, похоже, делают обратное.

В приведенном выше примере, если вы вызываете class_bar в классе Foo, вы получаете ошибку метода undefined, и если вы вызываете экземплярную строку экземпляра, возвращаемого Foo.new, вы также получаете ошибку метода undefined. Обе ошибки, похоже, противоречат интуитивному пониманию того, что должны делать class_eval и instance_eval.

В чем разница между этими методами?

Документация для class_eval:

mod.class_eval (string [, filename [, lineno]]) = > obj

Вычисляет строку или блок в контекст моды. Это можно использовать для добавьте методы в класс.

Документация для instance_eval:

obj.instance_eval {| | block} = > obj

Вычисляет строку, содержащую Ruby исходный код или заданный блок, в контексте приемника (OBJ). Чтобы установить контекст, переменная self устанавливается равной obj while код выполняет, выдавая код доступ к переменным экземпляра objs.

4b9b3361

Ответ 1

Как говорится в документации, class_eval оценивает строку или блок в контексте модуля или класса. Таким образом, следующие фрагменты кода эквивалентны:

class String
  def lowercase
    self.downcase
  end
end

String.class_eval do
  def lowercase
    self.downcase
  end
end

В каждом случае класс String был вновь открыт и определен новый метод. Этот метод доступен для всех экземпляров класса, поэтому:

"This Is Confusing".lowercase 
=> "this is confusing"
"The Smiths on Charlie Bus".lowercase
=> "the smiths on charlie bus"

class_eval имеет ряд преимуществ перед просто повторным открытием класса. Во-первых, вы можете легко называть его переменной, и это ясно, каковы ваши намерения. Другим преимуществом является то, что он не сработает, если класс не существует. Таким образом, приведенный ниже пример не будет выполнен, так как Array будет неправильно указан. Если класс просто был вновь открыт, он будет успешным (и будет определен новый неправильный класс Aray):

Aray.class_eval do
  include MyAmazingArrayExtensions
end

Наконец class_eval может взять строку, которая может быть полезна, если вы делаете что-то более гнусное...

instance_eval, с другой стороны, оценивает код для одного экземпляра объекта:

confusing = "This Is Confusing"
confusing.instance_eval do
  def lowercase
    self.downcase
  end
end   

confusing.lowercase
=> "this is confusing"
"The Smiths on Charlie Bus".lowercase
NoMethodError: undefined method ‘lowercase’ for "The Smiths on Charlie Bus":String

Итак, с помощью instance_eval метод определяется только для этого единственного экземпляра строки.

Итак, почему instance_eval в Class определяет методы класса?

Так же, как "This Is Confusing" и "The Smiths on Charlie Bus" являются экземплярами String, Array, String, Hash, а все остальные классы сами являются экземплярами Class. Вы можете проверить это, вызвав #class на них:

"This Is Confusing".class
=> String

String.class
=> Class

Поэтому, когда мы вызываем instance_eval, он делает то же самое в классе, что и на любом другом объекте. Если мы используем instance_eval для определения метода для класса, он определит метод только для этого экземпляра класса, а не для всех классов. Мы можем назвать этот метод методом класса, но это всего лишь метод экземпляра для этого конкретного класса.

Ответ 2

Другой ответ правильный, но позвольте мне немного углубиться.

Ruby имеет несколько разных областей; шесть в соответствии с wikipedia, хотя, как представляется, отсутствует документальная формальная документация. Неопределенно, виды охвата, связанные с этим вопросом, экземпляр и класс.

Текущая область экземпляра определяется значением self. Все неквалифицированные вызовы методов отправляются в текущий экземпляр, как и любые ссылки на переменные экземпляра (которые выглядят как @this).

Однако def не является вызовом метода. Целью методов, созданных def, является текущий класс (или модуль), который можно найти с помощью Module.nesting[0].

Посмотрите, как два разных вкуса eval влияют на эти области:

String.class_eval { [self, Module.nesting[0]] } => [String, String] String.instance_eval { [self, Module.nesting[0]] } => [String, #<Class:String>]

В обоих случаях область экземпляра - это объект, на который вызывается * _eval.

Для class_eval область видимости класса также становится целевым объектом, поэтому def создает методы экземпляра для этого класса/модуля.

Для instance_eval область видимости класса становится singleton class (aka metaclass, eigenclass) целевого объекта. Методы экземпляров, созданные в одноэлементном классе для объекта, становятся одноточечными методами для этого объекта. Методы Singleton для класса или модуля - это то, что обычно (и несколько неточно) называется методами класса.

Класс scope также используется для разрешения констант. Переменные класса (@@these @@things) разрешаются с помощью класса, но они пропускают одноэлементные классы при поиске в цепочке вложенности модуля. Единственный способ, которым я нашел доступ к переменным класса в одноэлементных классах, - class_variable_get/set.

Ответ 3

Я думаю, вы поняли это неправильно. class_eval добавляет метод в класс, поэтому все экземпляры будут иметь этот метод. instance_eval добавит метод только к одному конкретному объекту.

foo = Foo.new
foo.instance_eval do
  def instance_bar
    "instance_bar"
  end
end

foo.instance_bar      #=> "instance_bar"
baz = Foo.new
baz.instance_bar      #=> undefined method

Ответ 4

instance_eval эффективно создает метод singleton для рассматриваемого экземпляра объекта. class_eval создаст нормальный метод в данном контексте класса, доступный всем объектам этого класса.

Здесь ссылка на методы singleton и singleton pattern (не-рубиновый)