По сути, дизайнер интерфейса должен был сделать это из-за этого небольшого трюка с областями действия и тем, как работают eval
и instance_eval
, посмотрите этот пример:
Имеет 2 класса Foo
и Boo
со следующими определениями:
class Foo
def speak(phrase)
puts phrase
end
def self.generate(&block)
f = Foo.new
f.instance_eval(&block)
end
end
class Boo
attr_reader :name
def initialize(name) ; @name = name ; end
def express
Foo.generate { speak name}
end
end
Обычно это должно работать нормально в большинстве случаев, но в некоторых ситуациях, таких как следующий оператор, выдается ошибка:
Boo.new("someone").express #`express': undefined local variable or method `name' for #<Foo:0xb7f582fc> (NameError)
У нас нет здесь доступа к методам экземпляров Boo
внутри Foo
экземпляров, потому что мы используем instance_eval
, поэтому метод name
определен для Boo
экземпляры не входят в область действия Foo
экземпляров.
Чтобы преодолеть такие проблемы, лучше переопределить генерирование следующим образом:
class Foo
def speak(phrase)
puts phrase
end
def self.generate(&block)
f = Foo.new
block.arity < 1 ? f.instance_eval(&block) : block.call(f)
end
end
Это гибкий интерфейс, в котором вы оцениваете блок кода в зависимости от переданных параметров блока. Теперь мы должны передать текущий объект foo в качестве параметра, когда нам нужно вызвать методы экземпляра для него, давайте переопределим Boo
, проверим express
и talk
:
class Boo
attr_reader :name
def initialize(name) ; @name = name ; end
def express
Foo.generate { |f| f.speak name}
end
def talk(anything)
Foo.generate { speak anything}
end
end
Boo.new("someone").express #=> someone
Boo.new("someone").talk("whatever") #=> whatever