动态创建类方法



我正在编写一个类方法来创建另一个类。class_evalinstance_eval如何在类方法的上下文中操作似乎有些奇怪。举例说明:

class Test1
def self.add_foo
self.class_eval do # does what it says on the tin
define_method :foo do
puts "bar"
end
end
end
end
Test1.add_foo # creates new instance method, like I'd expect
Test1.new.foo # => "bar"

class Test2
def self.add_foo
self.instance_eval do # seems to do the same as "class_eval"
define_method :foo do
puts "bar"
end
end
end
end
Test2.add_foo # what is happening here?!
Test2.foo # => NoMethodError
Test2.new.foo # => "bar"

class Test3
def self.add_foo
(class << self; self; end).instance_eval do # call explicitly on metaclass
define_method :foo do
puts "bar"
end
end
end
end
Test3.add_foo # => creates new class method, as I'd expect
Test3.foo # => "bar"

我的理解是,类方法是在所讨论的类的元类(在本例中为Test2)上定义的实例方法。基于该逻辑,我希望类方法调用add_foo的接收器是元类。

  • selfTest2.add_foo方法中指的是什么
  • 为什么在此接收器对象上调用instance_eval会创建一个实例方法

instance_evalclass_eval之间的主要区别在于instance_eval在实例的上下文中工作,而class_eval在类的上下文中使用。我不确定您对Rails有多熟悉,但让我们来看一个例子:

class Test3 < ActiveRecord::Base
end
t = Test3.first
t.class_eval { belongs_to :test_25 } #=> Defines a relationship to test_25 for this instance
t.test_25 #=> Method is defined (but fails because of how belongs_to works)
t2 = Test3.find(2)
t2.test_25 #=> NoMethodError
t.class.class_eval { belongs_to :another_test }
t.another_test #=> returns an instance of another_test (assuming relationship exists)
t2.another_test #=> same as t.another_test
t.class_eval { id } #=> NameError
t.instance_eval { id } #=> returns the id of the instance
t.instance_eval { belongs_to :your_mom } #=> NoMethodError

之所以会发生这种情况,是因为belongs_to实际上是在类主体的上下文中发生的方法调用,您不能从实例中调用它。当您尝试用class_eval调用id时,它失败了,因为id是在实例上定义的方法,而不是在类中。

使用class_evalinstance_eval定义方法在对实例调用时基本上是一样的。它们将仅在调用对象的实例上定义方法。

t.class_eval do 
def some_method
puts "Hi!"
end
end
t.instance_eval do
def another_method
puts "Hello!"
end
end
t.some_method #=> "Hi!"
t.another_method #=> "Hello!"
t2.some_method #=> NoMethodError
t2.another_method #=> NoMethodError

然而,在处理阶级问题时,他们有所不同。

t.class.class_eval do
def meow
puts "meow!"
end
end
t.class.instance_eval do
def bark
puts "woof!"
end
end
t.meow #=> meow!
t2.meow #=> meow!
t.bark #=> NoMethodError
t2.bark #=> NoMethodError

树皮去哪儿了?它是在类"singleton类"的实例上定义的。我将在下面详细解释。但就目前而言:

t.class.bark #=> woof!
Test3.bark #=> woof!

因此,为了回答您关于self在类主体中指的是什么的问题,您可以构建一个小测试:

a = class Test4
def bar
puts "Now, I'm a #{self.inspect}"
end
def self.baz
puts "I'm a #{self.inspect}"
end
class << self
def foo
puts "I'm a #{self.inspect}"
end
def self.huh?
puts "Hmmm? indeed"
end
instance_eval do
define_method :razors do
puts "Sounds painful"
end
end
"But check this out, I'm a #{self.inspect}"
end
end
puts Test4.foo #=> "I'm a Test4"
puts Test4.baz #=> "I'm a Test4"
puts Test4.new.bar #=> Now I'm a #<Test4:0x007fa473358cd8>
puts a #=> But check this out, I'm a #<Class:Test4>

因此,在上面的第一个puts语句中,我们看到inspect告诉我们,类方法体上下文中的self引用了类Test4。在第二个puts中,我们看到了相同的东西,只是定义不同(使用self.method_name表示法来定义类方法)。在第三个例子中,我们看到self引用了Test4的一个实例。最后一个有点有趣,因为我们看到self指的是Class的一个名为Test4的实例。这是因为当您定义一个类时,您正在创建一个对象。Ruby中的所有东西都是一个对象。对象的这个实例被称为元类、本征类或单例类。

您可以使用class << self习惯用法访问本征类。当你在那里的时候,你实际上可以访问本征类的内部。您可以在本征类内部定义实例方法,这与调用self.method_name一致。但由于你在本征类的上下文中,你可以将方法附加到本征类中的本征类。

Test4.huh? #=> NoMethodError
Test4.singleton_class.huh? #=> Hmmm? indeed

当您在方法的上下文中调用instance_eval时,实际上是在类本身上调用instance_eval,这意味着您正在Test4上创建实例方法。我在本征类内部称instance_eval的地方呢?它在Test4的本征类实例上创建了一个方法:

Test4.razors #=> Sounds painful

希望这能澄清你的一些问题。我知道,我已经学会了一些事情打字这个答案!

最新更新