Почему вы можете вызывать instance_eval (в отличие от class_eval) внутри 'initialize'? - PullRequest
2 голосов
/ 14 сентября 2009
  class Observer
    def initialize(&block)
      instance_eval(&block) if block_given?
    end
  end 

Мне интересно, что делается здесь предположение о типе блока, который используется с 'initialize'.

Поскольку вызывается instance_eval, это означает, что блок оценивается в контексте класса Observer.

Почему он это делает, в отличие, скажем, от class_eval и что может быть результатом оценки блока в контексте класса?

Кроме того, как это будет вызвано?

Ответы [ 4 ]

9 голосов
/ 14 сентября 2009

Прежде всего, вы не можете сделать что-то вроде этого:

class Observer
  def initialize(&block)
    class_eval(&block) if block_given?
  end
end

Поскольку class_eval не определено для экземпляра Observer. Это определено в Module (от которого Class происходит). Мы вернемся к class_eval позже.

Причина использования вышеупомянутой идиомы часто заключается в том, чтобы разрешить инициализацию блока:

x = Observer.new do
  add_event(foo)
  some_other_instance_method_on_observer
  self.some_attribute = something
end

Кроме того, вы можете добавить методы к данному экземпляру класса:

foo = Observer.new do
  def foo
    'foo'
  end
end

foo.foo  # => "foo"

Вы можете сделать примерно то же самое без instance_eval:

class Foo
  def initialize
    yield self if block_given?
  end
end

foo = Foo.new do |x|
  x.add_event(foo)
  x.some_other_instance_method_on_observer
  x.self.some_attribute = something
end

Но это не дает вам возможности добавлять методы. Если вы должны были сделать это:

foo = Foo.new do
  def foo
    'foo'
  end
end

foo.foo  # => "foo"

Кажется, работает, верно? Но на самом деле вы добавили метод foo ко всему, потому что self установлен как «основной» объект. Это эквивалентно простому определению метода вне блока. Они добавляются как методы экземпляра к Object, поэтому они работают на всем.

Теперь, как и было обещано, краткий возврат к class_eval. Вы могли бы сделать что-то вроде этого:

class Observer
  def initialize(&block)
    class.class_eval(&block) if block_given?
  end
end

Но затем вы открываете весь класс:

x = Observer.new { def foo; 'foo'; end }
x.foo                                      # => "foo"
y = Observer.new
y.foo                                      # => "foo"

Обычно это не то, что мы хотим сделать. Плюс, self будет классом, а не экземпляром. Это делает его бесполезным для инициализации блока, как показано выше.

2 голосов
/ 14 сентября 2009

Одним из вариантов использования будет установка состояния наблюдателя в его контексте.

Возможно

o = Observer.new do
      listen_to(<some object>)
      report_to(<something>)
    end

Такое использование не будет работать с class_eval, который может получить доступ только к состоянию класса

0 голосов
/ 15 сентября 2009

Просто потому, что метод initialize является методом экземпляра, а class_eval определяется для объектов типа Class , что означает, что они могут выполняться только в методах класса внутри тела класса.

Таким образом, следующий фрагмент кода вызовет ошибку:

"". Class_eval {методы} # => NoMethodError: неопределенный метод class_eval для "": String

Пока этот просто будет работать:

s.class.class_eval{methods} #=> ["methods", "respond_to?", "module_eval", "class_variables", "dup", "instance_variables", "protected_instance_methods", "__id__", "public_method_defined?", "eql?", "object_id", "const_set", "id", "singleton_methods", "send", "class_eval", "taint", "include?", "private_instance_methods", "frozen?", "instance_variable_get", "private_method_defined?", "__send__", "instance_of?", "name", "to_a", "autoload", "type", "new", "protected_methods", "instance_eval", "display", "instance_method", "instance_variable_set", "kind_of?", "protected_method_defined?", "extend", "const_defined?", "to_s", "ancestors", "public_class_method", "allocate", "class", "<=>", "hash", "<", "tainted?", "private_methods", "==", "instance_methods", "===", "class_variable_defined?", ">", "nil?", "untaint", "constants", ">=", "is_a?", "autoload?", "<=", "inspect", "private_class_method", "const_missing", "method", "clone", "=~", "public_instance_methods", "public_methods", "method_defined?", "superclass", "instance_variable_defined?", "equal?", "freeze", "included_modules", "const_get"]
0 голосов
/ 14 сентября 2009

Initialize - это метод экземпляра. Он выполняется в контексте экземпляра, а не самого класса. Таким образом, instance_eval заставляет блок выполняться также в контексте экземпляра.

В обычном экземпляре объекта не существует такой вещи, как class_eval - она ​​определена только для классов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...